@sleep2agi/agent-network-dashboard 0.5.1-preview.7 → 0.5.1-preview.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/diagnostics/route-bundle-stats.json +4 -4
- package/.next/fallback-build-manifest.json +3 -3
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/admin.html +1 -1
- package/.next/server/app/admin.rsc +1 -1
- package/.next/server/app/admin.segments/_full.segment.rsc +1 -1
- package/.next/server/app/admin.segments/_head.segment.rsc +1 -1
- package/.next/server/app/admin.segments/_index.segment.rsc +1 -1
- package/.next/server/app/admin.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/admin.segments/admin/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/admin.segments/admin.segment.rsc +1 -1
- package/.next/server/app/index.html +2 -2
- package/.next/server/app/index.rsc +2 -2
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/index.segments/_full.segment.rsc +2 -2
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/login.html +2 -2
- package/.next/server/app/login.rsc +2 -2
- package/.next/server/app/login.segments/_full.segment.rsc +2 -2
- package/.next/server/app/login.segments/_head.segment.rsc +1 -1
- package/.next/server/app/login.segments/_index.segment.rsc +1 -1
- package/.next/server/app/login.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/login.segments/login.segment.rsc +1 -1
- package/.next/server/app/logs.html +1 -1
- package/.next/server/app/logs.rsc +1 -1
- package/.next/server/app/logs.segments/_full.segment.rsc +1 -1
- package/.next/server/app/logs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/logs.segments/_index.segment.rsc +1 -1
- package/.next/server/app/logs.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/logs.segments/logs/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/logs.segments/logs.segment.rsc +1 -1
- package/.next/server/app/messages.html +1 -1
- package/.next/server/app/messages.rsc +1 -1
- package/.next/server/app/messages.segments/_full.segment.rsc +1 -1
- package/.next/server/app/messages.segments/_head.segment.rsc +1 -1
- package/.next/server/app/messages.segments/_index.segment.rsc +1 -1
- package/.next/server/app/messages.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/messages.segments/messages/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/messages.segments/messages.segment.rsc +1 -1
- package/.next/server/app/node.html +1 -1
- package/.next/server/app/node.rsc +1 -1
- package/.next/server/app/node.segments/_full.segment.rsc +1 -1
- package/.next/server/app/node.segments/_head.segment.rsc +1 -1
- package/.next/server/app/node.segments/_index.segment.rsc +1 -1
- package/.next/server/app/node.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/node.segments/node/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/node.segments/node.segment.rsc +1 -1
- package/.next/server/app/nodes.html +1 -1
- package/.next/server/app/nodes.rsc +1 -1
- package/.next/server/app/nodes.segments/_full.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/_head.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/_index.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/nodes/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/nodes.segments/nodes.segment.rsc +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/server-logs.html +1 -1
- package/.next/server/app/server-logs.rsc +1 -1
- package/.next/server/app/server-logs.segments/_full.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/_head.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/_index.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/server-logs/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/server-logs.segments/server-logs.segment.rsc +1 -1
- package/.next/server/app/settings/networks.html +1 -1
- package/.next/server/app/settings/networks.rsc +1 -1
- package/.next/server/app/settings/networks.segments/_full.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/_index.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/settings/networks/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/settings/networks.segment.rsc +1 -1
- package/.next/server/app/settings/networks.segments/settings.segment.rsc +1 -1
- package/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/server/app/settings/tokens.html +1 -1
- package/.next/server/app/settings/tokens.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/_full.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/_index.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/settings/tokens/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/settings/tokens.segment.rsc +1 -1
- package/.next/server/app/settings/tokens.segments/settings.segment.rsc +1 -1
- package/.next/server/app/settings.html +2 -2
- package/.next/server/app/settings.rsc +2 -2
- package/.next/server/app/settings.segments/_full.segment.rsc +2 -2
- package/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/server/app/settings.segments/_index.segment.rsc +1 -1
- package/.next/server/app/settings.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/settings.segments/settings/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/settings.segments/settings.segment.rsc +1 -1
- package/.next/server/app/tasks.html +1 -1
- package/.next/server/app/tasks.rsc +1 -1
- package/.next/server/app/tasks.segments/_full.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/_head.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/_index.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/tasks/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/tasks.segments/tasks.segment.rsc +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js +3 -3
- package/.next/server/chunks/ssr/agent-network-dashboard_09kk21a._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_01jhlxz._.js.map +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js +1 -1
- package/.next/server/chunks/ssr/agent-network-dashboard_app_09d29my._.js.map +1 -1
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/static/chunks/{0bja1amnrg3li.js → 08fc_cz1nk7b9.js} +1 -1
- package/.next/static/chunks/{0wtq_6dnzems6.js → 0e0okm.affulg.js} +1 -1
- package/.next/static/chunks/0s3vtwfo26_t6.js +4 -0
- package/.next/trace +2 -2
- package/.next/trace-build +1 -1
- package/app/components/TopoGraph.tsx +36 -3
- package/package.json +1 -1
- package/scripts/topo-panel-rect-opacity-hover-test.mjs +109 -0
- package/scripts/topo-zoom-level-hover-letterspacing-test.mjs +91 -0
- package/.next/static/chunks/0k~uc0~~19hyy.js +0 -4
- /package/.next/static/{x9zCCrMkHsIYlXNY791KF → egukPz1ctU--4WnT2FpEU}/_buildManifest.js +0 -0
- /package/.next/static/{x9zCCrMkHsIYlXNY791KF → egukPz1ctU--4WnT2FpEU}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{x9zCCrMkHsIYlXNY791KF → egukPz1ctU--4WnT2FpEU}/_ssgManifest.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../agent-network-dashboard/node_modules/styled-jsx/dist/index/index.js","../../../../../agent-network-dashboard/node_modules/styled-jsx/style.js","../../../../../agent-network-dashboard/app/page.tsx","../../../../../agent-network-dashboard/app/components/StatsBar.tsx","../../../../../agent-network-dashboard/app/components/BroadcastBar.tsx","../../../../../agent-network-dashboard/app/components/TopoGraph.tsx","../../../../../agent-network-dashboard/app/components/ChatPopover.tsx","../../../../../agent-network-dashboard/app/lib/vendorIdentity.ts","../../../../../agent-network-dashboard/app/components/AgentCard.tsx","../../../../../agent-network-dashboard/app/components/InboxPanel.tsx","../../../../../agent-network-dashboard/app/components/LoadingSkeleton.tsx","../../../../../agent-network-dashboard/app/components/UserBar.tsx","../../../../../agent-network-dashboard/app/components/CommandCenter.tsx","../../../../../agent-network-dashboard/app/components/DispatchPanel.tsx"],"sourcesContent":["require('client-only');\nvar React = require('react');\n\nfunction _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }\n\nvar React__default = /*#__PURE__*/_interopDefaultLegacy(React);\n\n/*\nBased on Glamor's sheet\nhttps://github.com/threepointone/glamor/blob/667b480d31b3721a905021b26e1290ce92ca2879/src/sheet.js\n*/ function _defineProperties(target, props) {\n for(var i = 0; i < props.length; i++){\n var descriptor = props[i];\n descriptor.enumerable = descriptor.enumerable || false;\n descriptor.configurable = true;\n if (\"value\" in descriptor) descriptor.writable = true;\n Object.defineProperty(target, descriptor.key, descriptor);\n }\n}\nfunction _createClass(Constructor, protoProps, staticProps) {\n if (protoProps) _defineProperties(Constructor.prototype, protoProps);\n if (staticProps) _defineProperties(Constructor, staticProps);\n return Constructor;\n}\nvar isProd = typeof process !== \"undefined\" && process.env && process.env.NODE_ENV === \"production\";\nvar isString = function(o) {\n return Object.prototype.toString.call(o) === \"[object String]\";\n};\nvar StyleSheet = /*#__PURE__*/ function() {\n function StyleSheet(param) {\n var ref = param === void 0 ? {} : param, _name = ref.name, name = _name === void 0 ? \"stylesheet\" : _name, _optimizeForSpeed = ref.optimizeForSpeed, optimizeForSpeed = _optimizeForSpeed === void 0 ? isProd : _optimizeForSpeed;\n invariant$1(isString(name), \"`name` must be a string\");\n this._name = name;\n this._deletedRulePlaceholder = \"#\" + name + \"-deleted-rule____{}\";\n invariant$1(typeof optimizeForSpeed === \"boolean\", \"`optimizeForSpeed` must be a boolean\");\n this._optimizeForSpeed = optimizeForSpeed;\n this._serverSheet = undefined;\n this._tags = [];\n this._injected = false;\n this._rulesCount = 0;\n var node = typeof window !== \"undefined\" && document.querySelector('meta[property=\"csp-nonce\"]');\n this._nonce = node ? node.getAttribute(\"content\") : null;\n }\n var _proto = StyleSheet.prototype;\n _proto.setOptimizeForSpeed = function setOptimizeForSpeed(bool) {\n invariant$1(typeof bool === \"boolean\", \"`setOptimizeForSpeed` accepts a boolean\");\n invariant$1(this._rulesCount === 0, \"optimizeForSpeed cannot be when rules have already been inserted\");\n this.flush();\n this._optimizeForSpeed = bool;\n this.inject();\n };\n _proto.isOptimizeForSpeed = function isOptimizeForSpeed() {\n return this._optimizeForSpeed;\n };\n _proto.inject = function inject() {\n var _this = this;\n invariant$1(!this._injected, \"sheet already injected\");\n this._injected = true;\n if (typeof window !== \"undefined\" && this._optimizeForSpeed) {\n this._tags[0] = this.makeStyleTag(this._name);\n this._optimizeForSpeed = \"insertRule\" in this.getSheet();\n if (!this._optimizeForSpeed) {\n if (!isProd) {\n console.warn(\"StyleSheet: optimizeForSpeed mode not supported falling back to standard mode.\");\n }\n this.flush();\n this._injected = true;\n }\n return;\n }\n this._serverSheet = {\n cssRules: [],\n insertRule: function(rule, index) {\n if (typeof index === \"number\") {\n _this._serverSheet.cssRules[index] = {\n cssText: rule\n };\n } else {\n _this._serverSheet.cssRules.push({\n cssText: rule\n });\n }\n return index;\n },\n deleteRule: function(index) {\n _this._serverSheet.cssRules[index] = null;\n }\n };\n };\n _proto.getSheetForTag = function getSheetForTag(tag) {\n if (tag.sheet) {\n return tag.sheet;\n }\n // this weirdness brought to you by firefox\n for(var i = 0; i < document.styleSheets.length; i++){\n if (document.styleSheets[i].ownerNode === tag) {\n return document.styleSheets[i];\n }\n }\n };\n _proto.getSheet = function getSheet() {\n return this.getSheetForTag(this._tags[this._tags.length - 1]);\n };\n _proto.insertRule = function insertRule(rule, index) {\n invariant$1(isString(rule), \"`insertRule` accepts only strings\");\n if (typeof window === \"undefined\") {\n if (typeof index !== \"number\") {\n index = this._serverSheet.cssRules.length;\n }\n this._serverSheet.insertRule(rule, index);\n return this._rulesCount++;\n }\n if (this._optimizeForSpeed) {\n var sheet = this.getSheet();\n if (typeof index !== \"number\") {\n index = sheet.cssRules.length;\n }\n // this weirdness for perf, and chrome's weird bug\n // https://stackoverflow.com/questions/20007992/chrome-suddenly-stopped-accepting-insertrule\n try {\n sheet.insertRule(rule, index);\n } catch (error) {\n if (!isProd) {\n console.warn(\"StyleSheet: illegal rule: \\n\\n\" + rule + \"\\n\\nSee https://stackoverflow.com/q/20007992 for more info\");\n }\n return -1;\n }\n } else {\n var insertionPoint = this._tags[index];\n this._tags.push(this.makeStyleTag(this._name, rule, insertionPoint));\n }\n return this._rulesCount++;\n };\n _proto.replaceRule = function replaceRule(index, rule) {\n if (this._optimizeForSpeed || typeof window === \"undefined\") {\n var sheet = typeof window !== \"undefined\" ? this.getSheet() : this._serverSheet;\n if (!rule.trim()) {\n rule = this._deletedRulePlaceholder;\n }\n if (!sheet.cssRules[index]) {\n // @TBD Should we throw an error?\n return index;\n }\n sheet.deleteRule(index);\n try {\n sheet.insertRule(rule, index);\n } catch (error) {\n if (!isProd) {\n console.warn(\"StyleSheet: illegal rule: \\n\\n\" + rule + \"\\n\\nSee https://stackoverflow.com/q/20007992 for more info\");\n }\n // In order to preserve the indices we insert a deleteRulePlaceholder\n sheet.insertRule(this._deletedRulePlaceholder, index);\n }\n } else {\n var tag = this._tags[index];\n invariant$1(tag, \"old rule at index `\" + index + \"` not found\");\n tag.textContent = rule;\n }\n return index;\n };\n _proto.deleteRule = function deleteRule(index) {\n if (typeof window === \"undefined\") {\n this._serverSheet.deleteRule(index);\n return;\n }\n if (this._optimizeForSpeed) {\n this.replaceRule(index, \"\");\n } else {\n var tag = this._tags[index];\n invariant$1(tag, \"rule at index `\" + index + \"` not found\");\n tag.parentNode.removeChild(tag);\n this._tags[index] = null;\n }\n };\n _proto.flush = function flush() {\n this._injected = false;\n this._rulesCount = 0;\n if (typeof window !== \"undefined\") {\n this._tags.forEach(function(tag) {\n return tag && tag.parentNode.removeChild(tag);\n });\n this._tags = [];\n } else {\n // simpler on server\n this._serverSheet.cssRules = [];\n }\n };\n _proto.cssRules = function cssRules() {\n var _this = this;\n if (typeof window === \"undefined\") {\n return this._serverSheet.cssRules;\n }\n return this._tags.reduce(function(rules, tag) {\n if (tag) {\n rules = rules.concat(Array.prototype.map.call(_this.getSheetForTag(tag).cssRules, function(rule) {\n return rule.cssText === _this._deletedRulePlaceholder ? null : rule;\n }));\n } else {\n rules.push(null);\n }\n return rules;\n }, []);\n };\n _proto.makeStyleTag = function makeStyleTag(name, cssString, relativeToTag) {\n if (cssString) {\n invariant$1(isString(cssString), \"makeStyleTag accepts only strings as second parameter\");\n }\n var tag = document.createElement(\"style\");\n if (this._nonce) tag.setAttribute(\"nonce\", this._nonce);\n tag.type = \"text/css\";\n tag.setAttribute(\"data-\" + name, \"\");\n if (cssString) {\n tag.appendChild(document.createTextNode(cssString));\n }\n var head = document.head || document.getElementsByTagName(\"head\")[0];\n if (relativeToTag) {\n head.insertBefore(tag, relativeToTag);\n } else {\n head.appendChild(tag);\n }\n return tag;\n };\n _createClass(StyleSheet, [\n {\n key: \"length\",\n get: function get() {\n return this._rulesCount;\n }\n }\n ]);\n return StyleSheet;\n}();\nfunction invariant$1(condition, message) {\n if (!condition) {\n throw new Error(\"StyleSheet: \" + message + \".\");\n }\n}\n\nfunction hash(str) {\n var _$hash = 5381, i = str.length;\n while(i){\n _$hash = _$hash * 33 ^ str.charCodeAt(--i);\n }\n /* JavaScript does bitwise operations (like XOR, above) on 32-bit signed\n * integers. Since we want the results to be always positive, convert the\n * signed int to an unsigned by doing an unsigned bitshift. */ return _$hash >>> 0;\n}\nvar stringHash = hash;\n\nvar sanitize = function(rule) {\n return rule.replace(/\\/style/gi, \"\\\\/style\");\n};\nvar cache = {};\n/**\n * computeId\n *\n * Compute and memoize a jsx id from a basedId and optionally props.\n */ function computeId(baseId, props) {\n if (!props) {\n return \"jsx-\" + baseId;\n }\n var propsToString = String(props);\n var key = baseId + propsToString;\n if (!cache[key]) {\n cache[key] = \"jsx-\" + stringHash(baseId + \"-\" + propsToString);\n }\n return cache[key];\n}\n/**\n * computeSelector\n *\n * Compute and memoize dynamic selectors.\n */ function computeSelector(id, css) {\n var selectoPlaceholderRegexp = /__jsx-style-dynamic-selector/g;\n // Sanitize SSR-ed CSS.\n // Client side code doesn't need to be sanitized since we use\n // document.createTextNode (dev) and the CSSOM api sheet.insertRule (prod).\n if (typeof window === \"undefined\") {\n css = sanitize(css);\n }\n var idcss = id + css;\n if (!cache[idcss]) {\n cache[idcss] = css.replace(selectoPlaceholderRegexp, id);\n }\n return cache[idcss];\n}\n\nfunction mapRulesToStyle(cssRules, options) {\n if (options === void 0) options = {};\n return cssRules.map(function(args) {\n var id = args[0];\n var css = args[1];\n return /*#__PURE__*/ React__default[\"default\"].createElement(\"style\", {\n id: \"__\" + id,\n // Avoid warnings upon render with a key\n key: \"__\" + id,\n nonce: options.nonce ? options.nonce : undefined,\n dangerouslySetInnerHTML: {\n __html: css\n }\n });\n });\n}\nvar StyleSheetRegistry = /*#__PURE__*/ function() {\n function StyleSheetRegistry(param) {\n var ref = param === void 0 ? {} : param, _styleSheet = ref.styleSheet, styleSheet = _styleSheet === void 0 ? null : _styleSheet, _optimizeForSpeed = ref.optimizeForSpeed, optimizeForSpeed = _optimizeForSpeed === void 0 ? false : _optimizeForSpeed;\n this._sheet = styleSheet || new StyleSheet({\n name: \"styled-jsx\",\n optimizeForSpeed: optimizeForSpeed\n });\n this._sheet.inject();\n if (styleSheet && typeof optimizeForSpeed === \"boolean\") {\n this._sheet.setOptimizeForSpeed(optimizeForSpeed);\n this._optimizeForSpeed = this._sheet.isOptimizeForSpeed();\n }\n this._fromServer = undefined;\n this._indices = {};\n this._instancesCounts = {};\n }\n var _proto = StyleSheetRegistry.prototype;\n _proto.add = function add(props) {\n var _this = this;\n if (undefined === this._optimizeForSpeed) {\n this._optimizeForSpeed = Array.isArray(props.children);\n this._sheet.setOptimizeForSpeed(this._optimizeForSpeed);\n this._optimizeForSpeed = this._sheet.isOptimizeForSpeed();\n }\n if (typeof window !== \"undefined\" && !this._fromServer) {\n this._fromServer = this.selectFromServer();\n this._instancesCounts = Object.keys(this._fromServer).reduce(function(acc, tagName) {\n acc[tagName] = 0;\n return acc;\n }, {});\n }\n var ref = this.getIdAndRules(props), styleId = ref.styleId, rules = ref.rules;\n // Deduping: just increase the instances count.\n if (styleId in this._instancesCounts) {\n this._instancesCounts[styleId] += 1;\n return;\n }\n var indices = rules.map(function(rule) {\n return _this._sheet.insertRule(rule);\n })// Filter out invalid rules\n .filter(function(index) {\n return index !== -1;\n });\n this._indices[styleId] = indices;\n this._instancesCounts[styleId] = 1;\n };\n _proto.remove = function remove(props) {\n var _this = this;\n var styleId = this.getIdAndRules(props).styleId;\n invariant(styleId in this._instancesCounts, \"styleId: `\" + styleId + \"` not found\");\n this._instancesCounts[styleId] -= 1;\n if (this._instancesCounts[styleId] < 1) {\n var tagFromServer = this._fromServer && this._fromServer[styleId];\n if (tagFromServer) {\n tagFromServer.parentNode.removeChild(tagFromServer);\n delete this._fromServer[styleId];\n } else {\n this._indices[styleId].forEach(function(index) {\n return _this._sheet.deleteRule(index);\n });\n delete this._indices[styleId];\n }\n delete this._instancesCounts[styleId];\n }\n };\n _proto.update = function update(props, nextProps) {\n this.add(nextProps);\n this.remove(props);\n };\n _proto.flush = function flush() {\n this._sheet.flush();\n this._sheet.inject();\n this._fromServer = undefined;\n this._indices = {};\n this._instancesCounts = {};\n };\n _proto.cssRules = function cssRules() {\n var _this = this;\n var fromServer = this._fromServer ? Object.keys(this._fromServer).map(function(styleId) {\n return [\n styleId,\n _this._fromServer[styleId]\n ];\n }) : [];\n var cssRules = this._sheet.cssRules();\n return fromServer.concat(Object.keys(this._indices).map(function(styleId) {\n return [\n styleId,\n _this._indices[styleId].map(function(index) {\n return cssRules[index].cssText;\n }).join(_this._optimizeForSpeed ? \"\" : \"\\n\")\n ];\n })// filter out empty rules\n .filter(function(rule) {\n return Boolean(rule[1]);\n }));\n };\n _proto.styles = function styles(options) {\n return mapRulesToStyle(this.cssRules(), options);\n };\n _proto.getIdAndRules = function getIdAndRules(props) {\n var css = props.children, dynamic = props.dynamic, id = props.id;\n if (dynamic) {\n var styleId = computeId(id, dynamic);\n return {\n styleId: styleId,\n rules: Array.isArray(css) ? css.map(function(rule) {\n return computeSelector(styleId, rule);\n }) : [\n computeSelector(styleId, css)\n ]\n };\n }\n return {\n styleId: computeId(id),\n rules: Array.isArray(css) ? css : [\n css\n ]\n };\n };\n /**\n * selectFromServer\n *\n * Collects style tags from the document with id __jsx-XXX\n */ _proto.selectFromServer = function selectFromServer() {\n var elements = Array.prototype.slice.call(document.querySelectorAll('[id^=\"__jsx-\"]'));\n return elements.reduce(function(acc, element) {\n var id = element.id.slice(2);\n acc[id] = element;\n return acc;\n }, {});\n };\n return StyleSheetRegistry;\n}();\nfunction invariant(condition, message) {\n if (!condition) {\n throw new Error(\"StyleSheetRegistry: \" + message + \".\");\n }\n}\nvar StyleSheetContext = /*#__PURE__*/ React.createContext(null);\nStyleSheetContext.displayName = \"StyleSheetContext\";\nfunction createStyleRegistry() {\n return new StyleSheetRegistry();\n}\nfunction StyleRegistry(param) {\n var configuredRegistry = param.registry, children = param.children;\n var rootRegistry = React.useContext(StyleSheetContext);\n var ref = React.useState(function() {\n return rootRegistry || configuredRegistry || createStyleRegistry();\n }), registry = ref[0];\n return /*#__PURE__*/ React__default[\"default\"].createElement(StyleSheetContext.Provider, {\n value: registry\n }, children);\n}\nfunction useStyleRegistry() {\n return React.useContext(StyleSheetContext);\n}\n\n// Opt-into the new `useInsertionEffect` API in React 18, fallback to `useLayoutEffect`.\n// https://github.com/reactwg/react-18/discussions/110\nvar useInsertionEffect = React__default[\"default\"].useInsertionEffect || React__default[\"default\"].useLayoutEffect;\nvar defaultRegistry = typeof window !== \"undefined\" ? createStyleRegistry() : undefined;\nfunction JSXStyle(props) {\n var registry = defaultRegistry ? defaultRegistry : useStyleRegistry();\n // If `registry` does not exist, we do nothing here.\n if (!registry) {\n return null;\n }\n if (typeof window === \"undefined\") {\n registry.add(props);\n return null;\n }\n useInsertionEffect(function() {\n registry.add(props);\n return function() {\n registry.remove(props);\n };\n // props.children can be string[], will be striped since id is identical\n }, [\n props.id,\n String(props.dynamic)\n ]);\n return null;\n}\nJSXStyle.dynamic = function(info) {\n return info.map(function(tagInfo) {\n var baseId = tagInfo[0];\n var props = tagInfo[1];\n return computeId(baseId, props);\n }).join(\" \");\n};\n\nexports.StyleRegistry = StyleRegistry;\nexports.createStyleRegistry = createStyleRegistry;\nexports.style = JSXStyle;\nexports.useStyleRegistry = useStyleRegistry;\n","module.exports = require('./dist/index').style\n","'use client';\n\nimport { useEffect, useState } from 'react';\nimport Link from 'next/link';\nimport { formatUptime, previewContent } from './components/utils';\nimport { StatsBar } from './components/StatsBar';\nimport { BroadcastBar } from './components/BroadcastBar';\nimport { TopoGraph } from './components/TopoGraph';\nimport { AgentCard } from './components/AgentCard';\nimport { InboxPanel } from './components/InboxPanel';\nimport { LoadingSkeleton } from './components/LoadingSkeleton';\nimport { NodesEmptyState as EmptyState } from './components/EmptyState';\nimport { AliasAvatar } from './components/AliasAvatar';\nimport { STATUS_DOT_HEX, STATUS_CHIP_CLASS } from './lib/status';\nimport { UserBar } from './components/UserBar';\nimport { CommandCenter, useCommandCenter } from './components/CommandCenter';\nimport { DispatchPanel } from './components/DispatchPanel';\nimport { useSessions, useHealth, useAnetConfig, useTasks, useStats } from './lib/hooks';\nimport { useSSE } from './lib/useSSE';\nimport { InboxMessage } from './components/types';\nimport { useSWRConfig } from 'swr';\n\nexport default function Dashboard() {\n // Auto-upgrade: if no V3 auth in session, force re-login to get user token\n useEffect(() => {\n const hasV3 = sessionStorage.getItem('anet_v3_auth');\n if (!hasV3) {\n // Try silent re-auth: logout old cookie + redirect to login\n fetch('/api/auth/logout', { method: 'POST' }).catch(() => {});\n window.location.assign('/login');\n }\n }, []);\n\n const { sessions, hint: sessHint, error: sessError, isLoading } = useSessions();\n const { health } = useHealth();\n const { config: anetConfig } = useAnetConfig();\n const { tasks } = useTasks({ limit: '500' });\n const { stats } = useStats();\n const [showTopo, setShowTopo] = useState(typeof window !== 'undefined' && window.innerWidth >= 1024);\n const [showConfig, setShowConfig] = useState(false);\n const cmd = useCommandCenter();\n const [showDispatch, setShowDispatch] = useState(false);\n const [inbox, setInbox] = useState<InboxMessage[]>([]);\n const [agentFilter, setAgentFilter] = useState<'all' | 'working' | 'idle' | 'offline'>('all');\n // #84: last node.renamed event — passed to TopoGraph so an open chat\n // popover follows the rename instead of pointing at a dead alias. `ts`\n // makes the effect re-fire even when the same from/to repeats.\n const [renameSignal, setRenameSignal] = useState<{ from: string; to: string; ts: number } | null>(null);\n const { mutate } = useSWRConfig();\n\n // SSE: instant revalidation on CommHub events\n // Opt-out via NEXT_PUBLIC_DISABLE_SSE=1 — avoids HTTP/1.1 head-of-line blocking on navigation\n const sseEnabled = process.env.NEXT_PUBLIC_DISABLE_SSE !== '1';\n const { connected: sseConnected, supported: sseSupported } = useSSE({\n url: '/api/hub/events',\n enabled: sseEnabled,\n onEvent: (event) => {\n // #84 (RFC-010 §3.4): node.renamed — revalidate the session list so the\n // new alias propagates instantly (TopoGraph + node grid re-render, the\n // avatar hue recomputes as a pure fn of alias) instead of waiting for\n // the next 5s poll. The renamed node's history keeps the old alias\n // server-side, so the task list needs no revalidation here.\n if (event.type === 'node.renamed') {\n mutate('/api/hub/status');\n const d = event.data as { old_alias?: string; new_alias?: string } | undefined;\n if (d?.old_alias && d?.new_alias) {\n setRenameSignal({ from: d.old_alias, to: d.new_alias, ts: Date.now() });\n }\n return;\n }\n if (['new_task', 'new_message', 'new_reply', 'node_status_changed', 'broadcast'].includes(event.type)) {\n mutate('/api/hub/status');\n mutate((key: string) => typeof key === 'string' && key.startsWith('/api/hub/tasks'), undefined, { revalidate: true });\n }\n },\n });\n\n // Fetch inbox (not in SWR since it accumulates)\n useEffect(() => {\n const fetchInbox = () => {\n fetch('/api/hub/inbox').then(r => r.json()).then(data => {\n if (data.messages?.length) setInbox(prev => {\n const ids = new Set(prev.map(m => m.id));\n const newMsgs = data.messages.filter((m: { id: string }) => !ids.has(m.id));\n return [...newMsgs, ...prev].slice(0, 100);\n });\n }).catch(() => {});\n };\n fetchInbox();\n const interval = setInterval(fetchInbox, 10000);\n return () => clearInterval(interval);\n }, []);\n\n if (isLoading) return <LoadingSkeleton />;\n\n const sseSessions = health?.sse_sessions || {};\n // SSE keys are `network_id:alias` since v0.7+ (per-network scoping).\n // Fall back to alias-only for legacy hubs.\n const sseLookup = (s: { alias: string; network_id?: string }) =>\n (s.network_id ? sseSessions[`${s.network_id}:${s.alias}`] : undefined) ?? sseSessions[s.alias];\n // Online = status is not 'offline' (not just SSE-connected)\n const isOnline = (s: { alias: string; status: string; network_id?: string }) => s.status !== 'offline' || !!sseLookup(s);\n const online = sessions.filter(isOnline).length;\n const total = sessions.length;\n const working = sessions.filter(s => s.status === 'working').length;\n const uptime = health ? formatUptime(health.uptime) : '--';\n const version = health?.version || '--';\n const configHealthy = Boolean(anetConfig?.hub && anetConfig.tokenConfigured);\n const configSourceLabel =\n anetConfig?.source === 'file' ? 'Local config'\n : anetConfig?.source === 'runtime-env' ? 'Runtime env'\n : 'Config missing';\n\n // Task stats: prefer /api/stats, fallback to manual\n const taskStats: Record<string, number> = {};\n if (stats?.tasks?.by_status?.length) {\n for (const s of stats.tasks.by_status) {\n taskStats[s.status] = s.count;\n }\n } else {\n for (const t of tasks) {\n taskStats[t.status] = (taskStats[t.status] || 0) + 1;\n }\n }\n\n const sortedSessions = [...sessions].sort((a, b) => {\n const aOnline = isOnline(a) ? 1 : 0;\n const bOnline = isOnline(b) ? 1 : 0;\n if (aOnline !== bOnline) return bOnline - aOnline;\n const aWorking = a.status === 'working' ? 1 : 0;\n const bWorking = b.status === 'working' ? 1 : 0;\n return bWorking - aWorking;\n });\n\n /** Round 70: when the fleet is empty, the Overview reorders to lead with\n * the \"Spin up your first agent\" CTA and hides the Quick Navigation /\n * Nav rail / Broadcast bar that would otherwise occupy prime real estate\n * with zeros and dead-end links. Computed once, used as a gate below. */\n const fleetEmpty = sessions.length === 0 && !sessError;\n\n return (\n <div className=\"min-h-screen bg-[#0a0a1a] text-gray-100 p-4 sm:p-6 font-mono\">\n <div className=\"lg:ml-0 ml-10\">\n <StatsBar online={online} working={working} total={total} version={version} uptime={uptime} />\n </div>\n\n {/* Dispatch + User Bar — Dispatch hidden when fleet empty (nothing to\n dispatch to); UserBar still useful (account/sign-out menu). */}\n <div className=\"flex items-center gap-3 mb-4\">\n {!fleetEmpty && (\n <button onClick={() => setShowDispatch(true)}\n className=\"px-4 py-2 bg-gradient-to-r from-cyan-600 to-blue-600 hover:from-cyan-500 hover:to-blue-500 text-white text-sm font-medium rounded-xl shadow-lg shadow-cyan-500/10 transition-all active:scale-95 shrink-0\">\n ⚡ Dispatch\n </button>\n )}\n <div className=\"flex-1\"><UserBar /></div>\n </div>\n\n {/* anet config (collapsed by default) */}\n <section className=\"mb-6 rounded-lg border border-[#2a2a4a] bg-[#111128] px-4 py-3 shadow-lg shadow-black/20\">\n <button onClick={() => setShowConfig(!showConfig)} className=\"w-full flex items-center justify-between text-left\">\n <div className=\"flex items-center gap-2 text-xs\">\n <span className=\"uppercase text-gray-600\">Config</span>\n <span className={`w-2 h-2 rounded-full ${configHealthy ? 'bg-green-400' : 'bg-red-400'}`} />\n <span className=\"text-gray-500\">{configSourceLabel}</span>\n </div>\n <svg className={`w-4 h-4 text-gray-600 transition-transform ${showConfig ? 'rotate-180' : ''}`} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n </button>\n {showConfig && (\n <div className=\"mt-3 pt-3 border-t border-[#2a2a4a]\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"min-w-0\">\n <div className=\"text-gray-100 truncate text-sm\" title={anetConfig?.hub || undefined}>\n Hub: <span className={anetConfig?.hub ? 'text-cyan-300' : 'text-red-300'}>{anetConfig?.hub?.trim() || 'not configured'}</span>\n </div>\n </div>\n <div className=\"flex flex-wrap gap-2 text-xs\">\n <span className={`px-2.5 py-1 rounded-md border ${anetConfig?.tokenConfigured ? 'bg-blue-500/5 text-blue-300 border-blue-500/20' : 'bg-gray-500/5 text-gray-400 border-gray-500/20'}`}>\n Token: {anetConfig?.tokenPreview || 'not configured'}\n </span>\n </div>\n </div>\n {anetConfig?.error && <div className=\"mt-2 text-xs text-gray-600\">{anetConfig.error}</div>}\n </div>\n )}\n </section>\n\n {/* Task Status Stats */}\n {Object.keys(taskStats).length > 0 && (\n <section className=\"mb-6 rounded-lg border border-[#2a2a4a] bg-[#111128] px-4 py-3 shadow-lg shadow-black/20\">\n <div className=\"flex items-center justify-between mb-2\">\n <div className=\"text-xs uppercase text-gray-600\">Task Status</div>\n <Link href=\"/tasks\" prefetch={false} className=\"text-xs text-cyan-400 hover:text-cyan-300\">View all →</Link>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n {/* Round 69: order is \"hot first\" — active flow before terminal\n states — intentionally different from the lifecycle order on\n /tasks. Colors come from shared STATUS_CHIP_CLASS so a\n palette tweak in app/lib/status.ts updates here too.\n 'created' added in r69 (was missing — enum-coverage bug). */}\n {(['running', 'delivered', 'acked', 'replied', 'created', 'failed', 'cancelled', 'expired', 'closed'] as const)\n .filter((key) => taskStats[key])\n .map((key) => (\n <Link key={key} href={`/tasks?status=${key}`} prefetch={false} className={`px-2.5 py-1 rounded-md border text-xs ${STATUS_CHIP_CLASS[key]} hover:opacity-80 transition-opacity`}>\n {key}: {taskStats[key]}\n </Link>\n ))}\n </div>\n </section>\n )}\n\n {/* Quick Actions — split into two distinct intents:\n (1) Top: live stat cards (carry data, drill-in on click)\n (2) Bottom: pure nav rail (no number, icon + label)\n Round 24 — wrap both in a labelled block so the rhythm reads as\n \"here are the main jumps\" instead of two disconnected strips.\n Round 70 — entire Quick Nav + Nav rail + Broadcast + Recent\n Activity block is hidden when the fleet is empty so the\n onboarding CTA gets the page above the fold. */}\n {!fleetEmpty && <>\n <div className=\"text-[10px] uppercase tracking-[0.12em] text-gray-600 mb-2\">Quick navigation</div>\n <section className=\"mb-3 grid grid-cols-3 gap-2 sm:gap-3\">\n {(() => {\n // Build breakdown popover content per card. Pure data — pure CSS\n // popover (no JS state) wires the hover-show transition below.\n const idleCount = sessions.filter(s => isOnline(s) && s.status !== 'working').length;\n const offlineCount = total - online;\n const orderedStatuses = ['running', 'replied', 'failed', 'cancelled', 'expired', 'closed', 'created', 'delivered', 'acked'];\n const failedRecent = tasks.filter((t: { status: string }) => t.status === 'failed').length;\n\n const cards = [\n {\n href: '/nodes', label: 'Nodes',\n value: `${online}/${total}`,\n sub: `${total > 0 ? Math.round((online / total) * 100) : 0}% online`,\n color: 'text-green-400 border-green-500/20',\n popover: total === 0 ? null : [\n { k: 'working', v: working, dot: '', color: '#4ade80' },\n { k: 'idle', v: idleCount, dot: '', color: '#22d3ee' },\n { k: 'offline', v: offlineCount, dot: '', color: '#9ca3af' },\n ],\n },\n {\n href: '/tasks', label: 'Tasks',\n value: String(Object.values(taskStats).reduce((a, b) => a + b, 0) || 0),\n sub: 'all-time',\n color: 'text-cyan-400 border-cyan-500/20',\n popover: Object.keys(taskStats).length === 0 ? null\n : orderedStatuses\n .filter(s => taskStats[s])\n .map(s => {\n // Inline hex avoids Tailwind purging dynamic\n // `bg-${color}-400` class names.\n const hex = ({\n running: '#4ade80', replied: '#a78bfa', failed: '#f87171',\n cancelled: '#facc15', expired: '#fb923c', closed: '#9ca3af',\n created: '#9ca3af', delivered: '#60a5fa', acked: '#22d3ee',\n } as Record<string, string>)[s] || '#9ca3af';\n return { k: s, v: taskStats[s], dot: '', color: hex };\n }),\n },\n {\n href: '/tasks?status=failed', label: 'Failed',\n value: String(taskStats['failed'] || 0),\n sub: taskStats['failed'] ? 'needs review' : 'none',\n color: taskStats['failed'] ? 'text-red-400 border-red-500/25' : 'text-gray-500 border-gray-700/30',\n popover: !failedRecent ? [{ k: 'no failures yet', v: '', dot: '', color: '#4b5563' }]\n : [{ k: `${failedRecent} in current view`, v: '', dot: '', color: '#f87171' }],\n },\n ];\n\n return cards.map(a => (\n <Link\n key={a.href}\n href={a.href}\n prefetch={false}\n className={`anet-stat-link group relative rounded-xl border ${a.color} bg-[#111128] px-3 py-3 transition-all hover:-translate-y-px`}\n >\n <div className=\"flex items-baseline justify-between\">\n <div className={`text-xl font-semibold tabular-nums ${a.color.split(' ')[0]}`}>{a.value}</div>\n <div className=\"hidden sm:block text-[10px] text-gray-600 group-hover:text-gray-400 transition-colors\">View →</div>\n </div>\n <div className=\"text-[11px] text-gray-400 mt-0.5\">{a.label}</div>\n <div className=\"text-[10px] text-gray-600 mt-px\">{a.sub}</div>\n\n {/* Hover popover — CSS-only, restrained. Hidden on touch\n (no :hover) so mobile is unaffected. Positioned just\n below the card so it doesn't fight nav rail. */}\n {a.popover && a.popover.length > 0 && (\n <div className=\"anet-stat-popover hidden md:block pointer-events-none absolute left-2 right-2 top-full mt-1 z-20 rounded-lg border border-[#2a2a4a] bg-[#0d0d1a] shadow-lg shadow-black/30 px-3 py-2 opacity-0 translate-y-[-2px] group-hover:opacity-100 group-hover:translate-y-0 transition-all duration-150 delay-100\">\n <div className=\"text-[10px] text-gray-600 uppercase tracking-wider mb-1.5\">Breakdown</div>\n <ul className=\"space-y-1\">\n {a.popover.map(row => (\n <li key={row.k} className=\"flex items-center gap-2 text-[11px]\">\n <span\n className=\"inline-block w-1.5 h-1.5 rounded-full shrink-0\"\n style={{ backgroundColor: row.color }}\n aria-hidden\n />\n <span className=\"text-gray-400 capitalize\">{row.k}</span>\n {row.v !== '' && <span className=\"ml-auto text-gray-300 tabular-nums font-medium\">{row.v}</span>}\n </li>\n ))}\n </ul>\n </div>\n )}\n </Link>\n ));\n })()}\n </section>\n\n {/* Nav rail — pure navigation, icon + label, no data */}\n <section className=\"mb-6 grid grid-cols-3 gap-2 sm:gap-3\">\n {[\n { href: '/messages', label: 'Messages', icon: 'M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z' },\n { href: '/logs', label: 'Audit log', icon: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z M14 2v6h6 M16 13H8 M16 17H8 M10 9H8' },\n { href: '/admin', label: 'Admin', icon: 'M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4z M6 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2' },\n ].map(a => (\n <Link key={a.href} href={a.href} prefetch={false}\n className=\"anet-nav-tile flex items-center justify-center gap-2 rounded-xl border border-[#2a2a4a] bg-[#111128] px-3 py-2.5 text-[12px] text-gray-400 hover:text-gray-200 transition-colors\">\n <svg className=\"w-4 h-4 shrink-0\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d={a.icon} />\n </svg>\n <span>{a.label}</span>\n </Link>\n ))}\n </section>\n\n <BroadcastBar />\n\n {/* Recent Activity */}\n {tasks.length > 0 && (\n <section className=\"mb-6 bg-[#111128] border border-[#2a2a4a] rounded-xl p-4\">\n <div className=\"flex items-center justify-between mb-3\">\n <h2 className=\"text-sm font-semibold text-gray-300\">Recent Activity</h2>\n <Link href=\"/tasks\" className=\"text-xs text-cyan-400 hover:text-cyan-300\">All tasks →</Link>\n </div>\n <div className=\"space-y-2 max-h-40 overflow-y-auto\">\n {tasks.slice(0, 5).map((t: { task_id: string; from_name: string; to_name: string; status: string; content: string; created_at: string }) => (\n <div key={t.task_id} className=\"flex items-center gap-2 text-xs\">\n <span\n className=\"w-1.5 h-1.5 rounded-full shrink-0\"\n style={{ backgroundColor: STATUS_DOT_HEX[t.status] || '#6b7280' }}\n />\n {t.from_name && <AliasAvatar alias={t.from_name} size={14} />}\n <span className=\"text-gray-300 shrink-0 max-w-[20%] truncate\">{t.from_name || '?'}</span>\n <span className=\"text-gray-600\">→</span>\n {t.to_name && <AliasAvatar alias={t.to_name} size={14} />}\n <span className=\"text-gray-300 shrink-0 max-w-[20%] truncate\">{t.to_name || '?'}</span>\n <span className=\"text-gray-500 truncate flex-1\" title={t.content || ''}>{previewContent(t.content).slice(0, 60)}</span>\n <span className={`shrink-0 px-1.5 py-0.5 rounded text-[10px] border ${\n STATUS_CHIP_CLASS[t.status] || 'text-gray-500 border-gray-700/30'\n }`}>{t.status}</span>\n </div>\n ))}\n </div>\n </section>\n )}\n </>}\n\n {sessError && (\n <div className=\"bg-red-900/20 border border-red-800/40 text-red-300 px-4 py-3 rounded-lg mb-6 text-sm flex items-center justify-between\" role=\"alert\">\n <span>{String(sessError)}</span>\n <span className=\"text-gray-500 text-xs\">Check CommHub connection</span>\n </div>\n )}\n\n {sessions.length > 0 && (\n <div className=\"flex justify-center mb-4\">\n <button\n onClick={() => setShowTopo(!showTopo)}\n className=\"text-xs text-gray-500 hover:text-gray-300 border border-gray-700/50 px-4 py-1.5 rounded-lg transition-colors hover:border-gray-600 cursor-pointer\"\n >\n {showTopo ? 'Hide Topology' : 'Show Topology'}\n </button>\n </div>\n )}\n\n {/* Mobile hint when topo hidden */}\n {!showTopo && sessions.length > 0 && (\n <div className=\"lg:hidden text-center text-xs text-gray-600 mb-4\">\n Topology hidden on mobile for better readability\n </div>\n )}\n\n {showTopo && sessions.length > 0 && <TopoGraph sessions={sessions} sseSessions={sseSessions} renameSignal={renameSignal} />}\n\n {sessions.length === 0 && !sessError ? (\n <EmptyState\n hint={sessHint}\n taskHistoryCount={Object.values(taskStats).reduce((a, b) => a + b, 0)}\n />\n ) : (() => {\n const counts = {\n all: sortedSessions.length,\n working: sortedSessions.filter(s => isOnline(s) && s.status === 'working').length,\n idle: sortedSessions.filter(s => isOnline(s) && s.status !== 'working').length,\n offline: sortedSessions.filter(s => !isOnline(s)).length,\n };\n const filtered = sortedSessions.filter(s => {\n if (agentFilter === 'all') return true;\n if (agentFilter === 'offline') return !isOnline(s);\n if (agentFilter === 'working') return isOnline(s) && s.status === 'working';\n if (agentFilter === 'idle') return isOnline(s) && s.status !== 'working';\n return true;\n });\n // Filter chip color keyed to status (round 34): working=green, idle=cyan,\n // offline=gray, all=neutral. Inline hex dots avoid Tailwind v4 purge.\n const chips: { key: typeof agentFilter; label: string; dot?: string }[] = [\n { key: 'all', label: 'All' },\n { key: 'working', label: 'Working', dot: '#4ade80' },\n { key: 'idle', label: 'Idle', dot: '#22d3ee' },\n { key: 'offline', label: 'Offline', dot: '#6b7280' },\n ];\n return (\n <>\n <div className=\"mb-3 flex flex-wrap items-center gap-1\">\n {chips.map(c => (\n <button\n key={c.key}\n type=\"button\"\n onClick={() => setAgentFilter(c.key)}\n disabled={counts[c.key] === 0 && c.key !== 'all'}\n className={`flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs transition-colors disabled:opacity-30 disabled:cursor-not-allowed ${\n agentFilter === c.key\n ? 'bg-cyan-500/10 text-cyan-300 border border-cyan-500/30'\n : 'text-gray-500 hover:text-gray-200 hover:bg-[#1a1a2a] border border-transparent'\n }`}\n >\n {c.dot && <span aria-hidden className=\"inline-block w-1.5 h-1.5 rounded-full\" style={{ backgroundColor: c.dot }} />}\n <span>{c.label}</span>\n <span className={`text-[10px] tabular-nums ${agentFilter === c.key ? 'text-cyan-400' : 'text-gray-600'}`}>{counts[c.key]}</span>\n </button>\n ))}\n </div>\n {/* Round 48: previous breakpoints had `lg:grid-cols-2 xl:grid-cols-3`\n which kept lg (1024-1279px) at only 2 columns even though\n each AgentCard is fine ≥260px wide. With the sidebar (208px),\n main area at lg is ~816px so 3 cols at ~272px each fits.\n xl breakpoint auto-inherits lg=3 cols; 2xl bumps to 4. */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-3 sm:gap-4\">\n {filtered.map(s => (\n <AgentCard key={s.alias} session={s} hasSse={isOnline(s)} sseCount={sseLookup(s) || 0} onChat={cmd.openTab} />\n ))}\n </div>\n {filtered.length === 0 && (\n <div className=\"mt-4 text-center text-xs text-gray-600\">\n No agents match \"{agentFilter}\" — <button onClick={() => setAgentFilter('all')} className=\"underline hover:text-gray-400\">Show all</button>\n </div>\n )}\n </>\n );\n })()}\n\n <InboxPanel messages={inbox} />\n\n {/* Round 111 (issue #82): dropped the license badge — \"trial (12d\n left)\" read like a paywall countdown on an open-source dashboard\n and Vincent flagged it as misleading more than once. The SSE /\n polling dot stays: it's a real connection-status indicator, not\n a sales surface. */}\n <div className=\"mt-8 text-center text-xs text-gray-600 flex items-center justify-center gap-2 flex-wrap\">\n {sseSupported && (\n <>\n <span className={`w-1.5 h-1.5 rounded-full ${sseConnected ? 'bg-green-400' : 'bg-gray-600'}`} />\n {sseConnected ? 'SSE live' : 'SWR polling'}\n </>\n )}\n </div>\n\n {/* Dispatch Panel */}\n {showDispatch && <DispatchPanel sessions={sessions} onClose={() => setShowDispatch(false)} />}\n\n {/* Command Center (multi-tab chat) */}\n {cmd.tabs.length > 0 && (\n <CommandCenter\n tabs={cmd.tabs}\n activeTab={cmd.activeTab}\n onOpenTab={cmd.openTab}\n onCloseTab={cmd.closeTab}\n onSetActive={cmd.setActiveTab}\n onClose={cmd.closeAll}\n />\n )}\n </div>\n );\n}\n","'use client';\n\ninterface StatsBarProps {\n online: number;\n working: number;\n total: number;\n version: string;\n uptime: string;\n}\n\nexport function StatsBar({ online, working, total, version, uptime }: StatsBarProps) {\n const onlinePercent = total > 0 ? Math.round((online / total) * 100) : 0;\n const fleetEmpty = total === 0;\n\n return (\n <div className={fleetEmpty ? 'mb-4' : 'mb-8'}>\n {/* Title row */}\n <div className=\"flex flex-wrap items-center gap-3 mb-4\">\n <h1 className=\"text-2xl font-bold text-white tracking-tight\">Agent Network</h1>\n <span className=\"text-xs text-gray-500\">\n CommHub {version} · {uptime}\n </span>\n </div>\n\n {fleetEmpty ? (\n /* Round 72: thin status strip replaces the 4-card grid when fleet\n is empty. Saves ~280px on mobile (CTA y=650 → ~370) and keeps\n the same data visible in a single inline row. */\n <div className=\"anet-stat-strip flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-gray-500 border-t border-b border-[#2a2a4a] py-2\">\n <span className=\"inline-flex items-center gap-1.5\">\n <span aria-hidden className=\"inline-block w-1.5 h-1.5 rounded-full bg-gray-600\" />\n <span className=\"text-gray-300 tabular-nums\">0</span> online\n </span>\n <span className=\"text-gray-700\">·</span>\n <span className=\"inline-flex items-center gap-1.5\">\n <span aria-hidden className=\"inline-block w-1.5 h-1.5 rounded-full bg-gray-600\" />\n <span className=\"text-gray-300 tabular-nums\">0</span> working\n </span>\n <span className=\"text-gray-700\">·</span>\n <span className=\"inline-flex items-center gap-1.5\">\n <span aria-hidden className=\"inline-block w-1.5 h-1.5 rounded-full bg-gray-600\" />\n <span className=\"text-gray-300 tabular-nums\">0</span> registered\n </span>\n </div>\n ) : (\n /* Populated state — full 4-card grid as before */\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-3\">\n <StatCard\n value={online}\n label=\"Online\"\n sub={`${onlinePercent}% of fleet`}\n color=\"text-green-400\"\n accent=\"from-green-500/20 to-green-500/0\"\n border=\"border-green-500/15\"\n />\n <StatCard\n value={working}\n label=\"Working\"\n sub={online > 0 ? `${Math.round((working / online) * 100)}% utilization` : '--'}\n color=\"text-cyan-400\"\n accent=\"from-cyan-500/20 to-cyan-500/0\"\n border=\"border-cyan-500/15\"\n />\n <StatCard\n value={total - online}\n label=\"Offline\"\n sub={total - online === 0 ? 'All systems go' : `${total - online} disconnected`}\n color=\"text-gray-400\"\n accent=\"from-gray-500/10 to-gray-500/0\"\n border=\"border-gray-500/15\"\n />\n <StatCard\n value={total}\n label=\"Total\"\n sub=\"Registered nodes\"\n color=\"text-white\"\n accent=\"from-blue-500/15 to-blue-500/0\"\n border=\"border-blue-500/15\"\n />\n </div>\n )}\n </div>\n );\n}\n\nfunction StatCard({ value, label, sub, color, accent, border }: {\n value: number; label: string; sub: string; color: string; accent: string; border: string;\n}) {\n // Extract the color family (green/cyan/gray/blue/white) from `color` prop\n // so the light-theme top-strip CSS can pick the right accent.\n const accentKey = color.replace('text-', '').split('-')[0];\n return (\n <div\n data-anet-stat-card={accentKey}\n className={`anet-stat-card relative overflow-hidden rounded-xl border ${border} bg-[#111128] px-4 py-3 transition-all`}\n >\n <div className={`absolute inset-0 bg-gradient-to-br ${accent} pointer-events-none`} />\n <div className=\"relative\">\n <div className={`text-3xl font-bold ${color} tabular-nums leading-tight`}>{value}</div>\n <div className=\"text-sm text-gray-300 mt-0.5\">{label}</div>\n <div className=\"text-xs text-gray-600 mt-1\">{sub}</div>\n </div>\n </div>\n );\n}\n","'use client';\n\nimport { useState } from 'react';\n\nexport function BroadcastBar() {\n const [broadcastMsg, setBroadcastMsg] = useState('');\n const [broadcasting, setBroadcasting] = useState(false);\n const [broadcastResult, setBroadcastResult] = useState('');\n\n const sendBroadcast = async () => {\n if (!broadcastMsg.trim()) return;\n setBroadcasting(true);\n setBroadcastResult('');\n try {\n const res = await fetch('/api/hub/broadcast', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ message: broadcastMsg }),\n });\n const data = await res.json();\n if (data.ok) {\n setBroadcastResult(`Broadcast sent to ${data.recipients} node(s)`);\n setBroadcastMsg('');\n } else {\n setBroadcastResult(`Failed: ${data.error || 'Send error'}`);\n }\n } catch (e: unknown) {\n setBroadcastResult(`Failed: ${e instanceof Error ? e.message : 'Send error'}`);\n }\n setBroadcasting(false);\n setTimeout(() => setBroadcastResult(''), 5000);\n };\n\n return (\n <div className=\"mb-6\">\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={broadcastMsg}\n onChange={e => setBroadcastMsg(e.target.value)}\n onKeyDown={e => e.key === 'Enter' && sendBroadcast()}\n placeholder=\"Broadcast message to all online agents...\"\n maxLength={500}\n aria-label=\"Broadcast message\"\n className=\"flex-1 bg-[#111128] border border-[#2a2a4a] rounded-lg px-4 py-2.5 text-sm text-white placeholder-gray-600 focus:border-blue-500/50 focus:ring-1 focus:ring-blue-500/20 focus:outline-none transition-colors\"\n />\n <button\n onClick={sendBroadcast}\n disabled={broadcasting || !broadcastMsg.trim()}\n aria-label=\"Send broadcast\"\n className=\"flex items-center gap-2 px-5 py-2.5 bg-blue-600 hover:bg-blue-500 disabled:bg-gray-800 disabled:text-gray-600 text-white text-sm rounded-lg transition-all font-medium cursor-pointer disabled:cursor-not-allowed\"\n >\n {broadcasting ? (\n <>\n <svg aria-hidden className=\"w-4 h-4 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"3\" opacity=\"0.25\" />\n <path d=\"M4 12a8 8 0 018-8\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" />\n </svg>\n <span>Sending…</span>\n </>\n ) : (\n <>\n {/* Megaphone icon — \"broadcast to all\" affordance */}\n <svg aria-hidden className=\"w-4 h-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M3 11v3a1 1 0 0 0 1 1h2l3.29 3.29a1 1 0 0 0 1.71-.71V7.42a1 1 0 0 0-1.71-.71L6 10H4a1 1 0 0 0-1 1Z\" />\n <path d=\"M16 8a5 5 0 0 1 0 6\" opacity=\"0.7\" />\n <path d=\"M19 5a9 9 0 0 1 0 12\" opacity=\"0.45\" />\n </svg>\n <span>Broadcast</span>\n </>\n )}\n </button>\n </div>\n {broadcastMsg.length > 0 && (\n <div className=\"text-right text-xs text-gray-600 mt-1 tabular-nums\">{broadcastMsg.length}/500</div>\n )}\n {broadcastResult && (\n <div className={`mt-3 text-sm text-center anet-fade-in ${broadcastResult.startsWith('Failed') ? 'text-red-400' : 'text-green-400/80'}`}>\n {broadcastResult}\n </div>\n )}\n </div>\n );\n}\n","'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport useSWR from 'swr';\nimport { useRouter } from 'next/navigation';\nimport { Session } from './types';\nimport { aliasAvatarColors, aliasInitial } from './AliasAvatar';\nimport { ChatPopover } from './ChatPopover';\nimport { vendorForModel, runtimeIdentity, identityLine } from '../lib/vendorIdentity';\nimport { parseHubTime, relativeAgo } from '../lib/time';\n\n/** v0.10.0 Hero 1+2 / §3.F server-health hook — fetches the normalized\n * /api/hub/servers payload (preview.370 unblocked real-data via the\n * proxy schema-normalize layer) and exposes a per-hostname health\n * tier. red → worst-of-CPU/Mem/Disk ≥ 85%; amber → 60-85%; green\n * → < 60%; null when telemetry hasn't shipped yet OR the host is\n * offline.\n *\n * Shared with ServersDrawer via SWR's key-based dedup — both\n * consumers point at /api/hub/servers so the cache layer fans out\n * to a single fetch per refresh interval.\n */\ninterface ServerHealthRow {\n hostname: string;\n cpu_load_1min: number | null;\n cpu_cores: number;\n mem_used_gb: number | null;\n mem_total_gb: number | null;\n disk_used_gb: number | null;\n disk_total_gb: number | null;\n status: 'online' | 'offline';\n}\ntype ServerTier = 'red' | 'amber' | 'green';\nfunction classifyServer(s: ServerHealthRow): ServerTier | null {\n if (s.status === 'offline') return null;\n const cpuPct = s.cpu_load_1min != null && s.cpu_cores > 0 ? (s.cpu_load_1min / s.cpu_cores) * 100 : null;\n const memPct = s.mem_used_gb != null && s.mem_total_gb != null && s.mem_total_gb > 0 ? (s.mem_used_gb / s.mem_total_gb) * 100 : null;\n const diskPct = s.disk_used_gb != null && s.disk_total_gb != null && s.disk_total_gb > 0 ? (s.disk_used_gb / s.disk_total_gb) * 100 : null;\n const vals = [cpuPct, memPct, diskPct].filter((v): v is number => typeof v === 'number');\n if (vals.length === 0) return null;\n const worst = Math.max(...vals);\n if (worst >= 85) return 'red';\n if (worst >= 60) return 'amber';\n return 'green';\n}\nconst serversFetcher = async (url: string): Promise<{ servers: ServerHealthRow[] } | null> => {\n const res = await fetch(url);\n if (!res.ok) return null;\n return res.json();\n};\nfunction useServerHealthMap(): Map<string, ServerTier> {\n const { data } = useSWR<{ servers: ServerHealthRow[] } | null>(\n '/api/hub/servers',\n serversFetcher,\n { refreshInterval: 15000, dedupingInterval: 5000 },\n );\n return useMemo(() => {\n const m = new Map<string, ServerTier>();\n for (const s of data?.servers ?? []) {\n const tier = classifyServer(s);\n if (tier) m.set(s.hostname, tier);\n }\n return m;\n }, [data]);\n}\n\ninterface MessageFlow {\n from_alias: string;\n to_alias: string;\n content: string;\n created_at: string;\n}\n\ninterface TopoGraphProps {\n sessions: Session[];\n sseSessions: Record<string, number>;\n // #84: a node.renamed event from the Overview's SSE listener. When the\n // currently open chat popover targets `from`, it follows the rename to `to`.\n renameSignal?: { from: string; to: string; ts: number } | null;\n}\n\ninterface Point {\n x: number;\n y: number;\n}\n\ninterface FlowLink {\n key: string;\n from: string;\n to: string;\n count: number;\n content: string;\n /** ISO timestamp of the most recent message on this edge — drives\n * the Round 10 freshness fade so dormant links recede visually. */\n last_at: string;\n}\n\nconst cx = 500;\nconst cy = 330;\nconst onlineRadius = 220;\n// Round 97 (issue #50): tier into two rings when N > 8; round 98\n// (issue #61): tier into three rings when N > 14. At N=22 (Vincent's\n// network) two rings still leave 11 nodes per ring → inner chord of\n// 88px can't fit a 100px label; three rings give ~⌈N/3⌉ per ring so\n// every tier has enough arc room.\nconst onlineTierThreshold = 8;\nconst onlineTripleThreshold = 14;\nconst onlineInnerRadius = 175;\nconst onlineOuterRadius = 260;\nconst onlineTripleInnerR = 145;\nconst onlineTripleMidR = 215;\nconst onlineTripleOuterR = 285;\nconst offlineRadius = 325;\n\n/** Polar coordinate for a node on a ring. `rotateBy` lets the caller offset\n * the whole ring so two stacked rings don't align radially (round 25: the\n * offline ring gets a half-step rotation so its nodes sit in the gaps\n * between online ones). */\nfunction polarPoint(index: number, total: number, radius: number, rotateBy = 0) {\n const spread = total <= 2 ? Math.PI : Math.PI * 1.78;\n const start = -Math.PI / 2 - spread / 2;\n const angle = total <= 1 ? -Math.PI / 2 : start + (spread * index) / (total - 1) + rotateBy;\n return {\n x: cx + radius * Math.cos(angle),\n y: cy + radius * Math.sin(angle),\n };\n}\n\nfunction curvePath(from: Point, to: Point, lift = 0) {\n const mx = (from.x + to.x) / 2;\n const my = (from.y + to.y) / 2;\n const dx = to.x - from.x;\n const dy = to.y - from.y;\n const length = Math.hypot(dx, dy) || 1;\n const normalX = -dy / length;\n const normalY = dx / length;\n const control = {\n x: mx + normalX * lift,\n y: my + normalY * lift,\n };\n\n return `M${from.x},${from.y} Q${control.x},${control.y} ${to.x},${to.y}`;\n}\n\nfunction truncate(value: string, max: number) {\n return value.length > max ? `${value.slice(0, max - 1)}...` : value;\n}\n\n/** Round 46 / Loop: SWR-freshness chip.\n *\n * TopoGraph's data refreshes every 5 s via SWR but the user has no\n * visible signal that the canvas they're looking at is current. The\n * flow particles and chips (R42 active-links, R43 hub) tell you when\n * the FLEET last did something, not when the DATA last syncing. This\n * chip ticks `live · Ns` against a 1-second interval so the operator\n * can trust freshness without doing internal math.\n *\n * Owns its own setInterval so the parent topology doesn't re-render\n * every second (only this small chip does). lastSyncRef is updated\n * whenever the `sessions` prop reference changes — that's the SWR\n * refresh signal. */\nfunction FreshnessChip({ sessions }: { sessions: unknown }) {\n const lastSyncRef = useRef<number>(Date.now());\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n // sessions reference changed → SWR just delivered a new payload.\n lastSyncRef.current = Date.now();\n }, [sessions]);\n useEffect(() => {\n const id = setInterval(() => setNow(Date.now()), 1000);\n return () => clearInterval(id);\n }, []);\n const sec = Math.max(0, Math.floor((now - lastSyncRef.current) / 1000));\n // Tint the chip warmer when the data goes stale (>10s since last sync).\n // SWR's default refreshInterval here is 5s, so anything past ~10s is\n // either a poll miss or a network hiccup.\n const stale = sec > 10;\n // Round 187 / Loop: chip transitions between fresh (gray) and stale\n // (amber) colour palettes smoothly. Pre-R187 the className swap\n // snapped every time the stale boundary was crossed — could happen\n // multiple times per minute on a flaky network. Adding\n // transition-colors makes the stale-onset (gray → amber) and\n // recovery (amber → gray) ease through the bg / text / border\n // palette together. 300ms matches R161/R162 active-links chip\n // freshness fade timing for visual consistency in the chip row.\n // Round 315 / Loop: FreshnessChip joins the R313-R314 chip-row\n // data-weight family. When it appears (stale state only — R275\n // gated rendering to stale), it sits next to working/online/\n // active-links chips that all carry font-medium (R313) plus the\n // vendor letter chips (R314). Without font-medium the warning\n // chip would render at default 400 next to data chips at 500\n // — visual inconsistency right at the moment the chip exists to\n // grab attention. font-medium adds it to the HTML-context data\n // tier (R312-R314 family); the amber bg/text/border still does\n // the warning-state work, the weight just keeps the chip in the\n // same data-typography ladder as its siblings.\n const baseClass = \"hidden sm:inline px-2.5 py-1 rounded-md font-mono font-medium border transition-colors duration-300\";\n const colorClass = stale\n ? \"bg-amber-500/10 text-amber-300 border-amber-500/20\"\n : \"bg-gray-500/10 text-gray-400 border-gray-500/20\";\n /* Round 275 / Loop: simplification per Vincent 5214/5215-5217 visual\n audit (clutter cleanup for Twitter screenshot). Pre-R275 the chip\n ALWAYS rendered — \"live · 5s\" gray-on-gray at rest, \"lag · 15s\"\n amber when stale. The fresh state is an \"everything's fine\"\n affirmation that's implicit elsewhere on the canvas (counts\n updating, flows animating). Adding a permanent chip to the chip-\n row's right end for that affirmation is added visual chrome\n without proportional info value.\n\n R275 converts the chip to a CONDITIONAL warning indicator: render\n only when stale (sec > 10). Fresh state → null (chip absent). The\n amber stale chip still appears as a warning when SWR lags, so\n users see the problem signal; the fresh state implicitly relies\n on other liveness signals (recent-signal panel rows, edge\n animations, count updates).\n\n Net effect: chip-row at rest has 1 fewer chip (cleaner Twitter\n screenshot, less right-edge chrome), but signals appear on\n stale-onset to direct attention. */\n if (!stale) return null;\n return (\n <span\n className={`${baseClass} ${colorClass}`}\n title={stale ? `Last sync ${sec}s ago — SWR refresh may be lagging` : `Live data · refreshes every 5s · last sync ${sec}s ago`}\n data-freshness-chip\n data-freshness-chip-stale={stale ? 'true' : 'false'}\n >\n {/* Round 272 / Loop: swap prefix word to match color state so\n text and color point the same way. Pre-R272 the chip read\n \"live · {sec}s\" in BOTH fresh (gray) and stale (amber)\n states — the amber color signals \"concerning\" but \"live\"\n still says \"fresh data flowing\", a visual contradiction.\n Post-R272: fresh=\"live · {sec}s\" (gray + reassuring), stale=\n \"lag · {sec}s\" (amber + signals lagging). Same monospace\n cell count (3 chars + \" · \" + digits + \"s\") so no chip\n width jitter on threshold crossing; R187 transition-colors\n duration-300 still eases the bg/color flip. Title (hover\n tooltip) still spells out the full meaning in either\n state. */}\n {stale ? 'lag' : 'live'} · {sec}s\n </span>\n );\n}\n\n/** Round 36 / Loop: prefers-reduced-motion hook.\n *\n * Round 29's a11y sweep zeroed CSS animations via media query, but SVG\n * SMIL `<animate>` / `<animateMotion>` elements aren't reachable from CSS\n * — they need JS to opt out. This hook reads the media query and listens\n * for changes so the topology's flow particles, pulses, click ripple\n * and hub breath honour the OS-level preference. */\nfunction useReducedMotion(): boolean {\n const [reduced, setReduced] = useState(false);\n useEffect(() => {\n if (typeof window === 'undefined' || !window.matchMedia) return;\n const mq = window.matchMedia('(prefers-reduced-motion: reduce)');\n setReduced(mq.matches);\n const onChange = (e: MediaQueryListEvent) => setReduced(e.matches);\n mq.addEventListener('change', onChange);\n return () => mq.removeEventListener('change', onChange);\n }, []);\n return reduced;\n}\n\n// Round 38 / Loop: parseHubTime + relativeAgo factored to app/lib/time.ts\n// — the Round 35 fix lives there now alongside the Round 37 mirror so\n// any future TZ-safe parse update happens in one place.\n\n/** Round 12 / Loop: status trio audit.\n *\n * Each (status × theme) cell returns a {primary, halo, text} trio. The trio\n * invariant — keep it when adding states or tweaking shades:\n *\n * light: primary = <hue>-600 halo = <hue>-100 * text = <hue>-800/900\n * dark: primary = <hue>-400/500 halo = <hue>-900 text = <hue>-100\n *\n * * offline.halo light deviates intentionally to slate-200 (#e2e8f0):\n * slate-100 (#f1f5f9) is too close to the panel bg (#f8fafc) and the\n * halo would vanish. The other three rows keep the 100-shade.\n *\n * Audit caught one cross-family drift before this round: online-other halo\n * light was #dbeafe (blue-100) while its primary (#0284c7 sky-600) and text\n * (#0c4a6e sky-900) were sky-family — now sky-100 (#e0f2fe). Tiny visual\n * difference; large hygiene win — every trio is now mono-hue. */\nfunction nodeStatus(session: Session, isOnline: boolean, isLight: boolean) {\n if (!isOnline) {\n return {\n label: 'offline',\n primary: isLight ? '#94a3b8' : '#6b7280', // slate-400 / gray-500\n halo: isLight ? '#e2e8f0' : '#111827', // slate-200* / gray-900\n text: isLight ? '#475569' : '#9ca3af', // slate-600 / gray-400\n };\n }\n if (session.status === 'working') {\n return {\n label: 'working',\n primary: isLight ? '#059669' : '#22c55e', // emerald-600 / green-500\n halo: isLight ? '#d1fae5' : '#14532d', // emerald-100 / green-900\n text: isLight ? '#065f46' : '#dcfce7', // emerald-800 / green-100\n };\n }\n if (session.status === 'idle') {\n return {\n label: 'idle',\n primary: isLight ? '#0d9488' : '#2dd4bf', // teal-600 / teal-400\n halo: isLight ? '#ccfbf1' : '#134e4a', // teal-100 / teal-900\n text: isLight ? '#115e59' : '#ccfbf1', // teal-800 / teal-100\n };\n }\n return {\n label: session.status || 'online',\n primary: isLight ? '#0284c7' : '#38bdf8', // sky-600 / sky-400\n halo: isLight ? '#e0f2fe' : '#0c4a6e', // sky-100 / sky-900 (was blue-100 — drift fixed Round 12)\n text: isLight ? '#0c4a6e' : '#e0f2fe', // sky-900 / sky-100\n };\n}\n\n/** Theme-aware color palette for the topology SVG. */\ninterface Palette {\n panelStops: [string, string, string];\n radarStops: { color: string; opacity: number }[];\n arrowFill: string;\n ringStroke: string;\n spokeStroke: { active: string; idle: string };\n flowEdge: string;\n flowPath: string;\n flowParticle: string;\n nodeFill: { online: string; offline: string };\n labelBox: { fill: string; stroke: string };\n legendBox: { fill: string; stroke: string };\n legendText: string;\n legendHeadline: string;\n legendAccent: string;\n containerBg: string;\n containerBorder: string;\n topRailGradient: string;\n}\n\nconst DARK_PALETTE: Palette = {\n panelStops: ['#0b1220', '#080814', '#101018'],\n radarStops: [\n { color: '#22d3ee', opacity: 0.18 },\n { color: '#22c55e', opacity: 0.045 },\n { color: '#020617', opacity: 0 },\n ],\n arrowFill: '#67e8f9',\n ringStroke: '#164e63',\n spokeStroke: { active: '#22d3ee', idle: '#155e75' },\n flowEdge: '#67e8f9',\n flowPath: '#e0f2fe',\n flowParticle: '#fef08a',\n nodeFill: { online: '#020617', offline: '#080814' },\n labelBox: { fill: '#020617', stroke: '#1f2937' },\n legendBox: { fill: '#020617', stroke: '#1f2937' },\n legendText: '#94a3b8',\n legendHeadline: '#e5e7eb',\n legendAccent: '#67e8f9',\n containerBg: '#080814',\n containerBorder: '#2a2a4a',\n topRailGradient: 'from-transparent via-cyan-400/70 to-transparent',\n};\n\nconst LIGHT_PALETTE: Palette = {\n panelStops: ['#f8fafc', '#ffffff', '#f1f5f9'],\n radarStops: [\n { color: '#10b981', opacity: 0.06 },\n { color: '#3b82f6', opacity: 0.03 },\n { color: '#ffffff', opacity: 0 },\n ],\n arrowFill: '#10b981',\n ringStroke: '#cbd5e1',\n spokeStroke: { active: '#10b981', idle: '#cbd5e1' },\n flowEdge: '#10b981',\n flowPath: '#475569',\n flowParticle: '#f59e0b',\n nodeFill: { online: '#ffffff', offline: '#f8fafc' },\n labelBox: { fill: '#ffffff', stroke: '#e2e8f0' },\n legendBox: { fill: '#ffffff', stroke: '#e2e8f0' },\n legendText: '#475569',\n legendHeadline: '#0f172a',\n legendAccent: '#0d9488',\n containerBg: '#ffffff',\n containerBorder: '#e3e6eb',\n topRailGradient: 'from-transparent via-emerald-500/40 to-transparent',\n};\n\nfunction useTheme(): 'light' | 'dark' {\n const [theme, setTheme] = useState<'light' | 'dark'>('dark');\n useEffect(() => {\n const read = () => {\n const t = document.documentElement.getAttribute('data-theme') || 'cyber';\n setTheme(t === 'light' || t === 'mint' ? 'light' : 'dark');\n };\n read();\n const obs = new MutationObserver(read);\n obs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });\n return () => obs.disconnect();\n }, []);\n return theme;\n}\n\n/** Round 100 (issue #79): brand showcase mode. Activate via `?brand=intern`\n * on any dashboard page — TopoGraph nodes show the 书小生 mascot instead\n * of alias initials. Stored in localStorage so the flag persists across\n * navigation. Clears with `?brand=none` or `?brand=` (empty). */\nfunction useBrand(): string | null {\n const [brand, setBrand] = useState<string | null>(null);\n useEffect(() => {\n try {\n const url = new URL(window.location.href);\n const param = url.searchParams.get('brand');\n if (param !== null) {\n if (param === '' || param === 'none') {\n localStorage.removeItem('anet-brand');\n setBrand(null);\n } else {\n localStorage.setItem('anet-brand', param);\n setBrand(param);\n }\n } else {\n setBrand(localStorage.getItem('anet-brand'));\n }\n } catch {}\n }, []);\n return brand;\n}\n\n/** Round 106 (issue #83): cluster agents by shared alias prefix so a team\n * reads as one unit in the topology. Adjacent (sorted) aliases that share\n * a ≥2-char prefix join the same group; the group key is the prefix common\n * to every member. Singletons map to their own alias (no hue shift). The\n * group key feeds aliasAvatarColors() so e.g. all 通信* nodes get one hue,\n * all 研究员* another — the \"同色相 tint\" clustering option from #83. */\nfunction commonPrefix(a: string, b: string): string {\n let i = 0;\n while (i < a.length && i < b.length && a[i] === b[i]) i++;\n return a.slice(0, i);\n}\n\n/** #83 + #111: group nodes that share a ≥2-char alias prefix OR a project_dir\n * (\"either criterion → same group\", Vincent 4724). Union-find over the\n * sessions; the component label prefers the shared project_dir's basename,\n * else the common alias prefix. Returns alias → groupKey. */\nfunction computeGroups(sessions: { alias: string; project_dir?: string | null }[]): Record<string, string> {\n const n = sessions.length;\n const parent = sessions.map((_, i) => i);\n const find = (i: number): number => {\n while (parent[i] !== i) { parent[i] = parent[parent[i]]; i = parent[i]; }\n return i;\n };\n const union = (a: number, b: number) => {\n const ra = find(a), rb = find(b);\n if (ra !== rb) parent[ra] = rb;\n };\n\n // union by shared project_dir\n const byDir: Record<string, number[]> = {};\n sessions.forEach((s, i) => {\n const d = s.project_dir?.trim();\n if (d) (byDir[d] ||= []).push(i);\n });\n for (const idxs of Object.values(byDir)) {\n for (let k = 1; k < idxs.length; k++) union(idxs[0], idxs[k]);\n }\n\n // union by shared ≥2-char alias prefix — sort, link adjacent pairs\n const order = sessions.map((_, i) => i).sort((a, b) => sessions[a].alias.localeCompare(sessions[b].alias));\n for (let k = 0; k + 1 < order.length; k++) {\n if (commonPrefix(sessions[order[k]].alias, sessions[order[k + 1]].alias).length >= 2) {\n union(order[k], order[k + 1]);\n }\n }\n\n // label each connected component\n const comps: Record<number, number[]> = {};\n for (let i = 0; i < n; i++) (comps[find(i)] ||= []).push(i);\n const keys: Record<string, string> = {};\n for (const members of Object.values(comps)) {\n let label: string;\n if (members.length === 1) {\n label = sessions[members[0]].alias;\n } else {\n const dirs = new Set(members.map(i => sessions[i].project_dir?.trim()).filter(Boolean) as string[]);\n if (dirs.size === 1) {\n const d = [...dirs][0];\n label = d.split('/').filter(Boolean).pop() || d;\n } else {\n label = members.map(i => sessions[i].alias).reduce((a, b) => commonPrefix(a, b));\n if (label.length < 2) label = sessions[members[0]].alias;\n }\n }\n for (const i of members) keys[sessions[i].alias] = label;\n }\n return keys;\n}\n\nfunction buildFlowLinks(messages: MessageFlow[], positions: Record<string, Point>) {\n const links = new Map<string, FlowLink>();\n\n messages.forEach(message => {\n if (\n !positions[message.from_alias] ||\n !positions[message.to_alias] ||\n message.from_alias === message.to_alias\n ) {\n return;\n }\n\n const key = `${message.from_alias}->${message.to_alias}`;\n const current = links.get(key);\n\n // Keep the most-recent timestamp per pair so the render can fade\n // dormant edges (Round 10 freshness fade).\n const incoming = message.created_at || '';\n const last_at = !current\n ? incoming\n : (incoming > current.last_at ? incoming : current.last_at);\n\n links.set(key, {\n key,\n from: message.from_alias,\n to: message.to_alias,\n count: (current?.count || 0) + 1,\n content: current?.content || message.content,\n last_at,\n });\n });\n\n return [...links.values()].slice(0, 18);\n}\n\nexport function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProps) {\n const theme = useTheme();\n const isLight = theme === 'light';\n const reducedMotion = useReducedMotion();\n const pal = isLight ? LIGHT_PALETTE : DARK_PALETTE;\n const brand = useBrand();\n const isIntern = brand === 'intern';\n // v0.10.0 Hero 1+2 / §3.F — per-host health tier map. Composes\n // with #119 ServersDrawer (same SWR key, deduped). When a node's\n // host server crosses into the red tier (CPU/Mem/Disk ≥ 85%),\n // the per-node SVG render adds a faint amber outer ring to flag\n // the issue without leaving the topology view.\n const hostHealthMap = useServerHealthMap();\n // R133: Next.js client-router for the recent-signal panel \"+N more\"\n // navigation. TopoGraph hasn't needed routing before — every other\n // affordance composes back into the canvas's own state — but the\n // truncated-flow footer is the one place where the user logically\n // wants to leave: \"show me the rest of the flows\" → /messages.\n const router = useRouter();\n const [messages, setMessages] = useState<MessageFlow[]>([]);\n // Issue #87: ring | grid layout toggle. Ring is the tiered-radial default;\n // grid arranges nodes in an N×M grid (better for 30+ nodes). Persisted to\n // localStorage like the zoom/pan view state. Declared above nodePositions\n // since that useMemo branches on it.\n const [layout, setLayout] = useState<'ring' | 'grid'>('ring');\n useEffect(() => {\n try {\n const saved = localStorage.getItem('anet-topo-layout');\n if (saved === 'grid' || saved === 'ring') {\n setLayout(saved);\n } else if (sessions.length >= 20) {\n // v0.10.0 Hero 3 Wave 1 §3.D — auto-grid for dense fleets.\n // When the user hasn't explicitly chosen a layout (no\n // localStorage entry), default to `grid` once the fleet\n // crosses 20 nodes. Below 20, the ring layout reads more\n // attractive (per #87 + R97-99 tier-ring history); at 20+\n // the ring starts packing tier 3 (R98 triple-tier\n // threshold) and grid scales cleaner — every cell visible\n // at the same density, no overlap-test risk from tier\n // crowding. The user's explicit toggle (R163 chrome\n // Ring|Grid) always wins by writing to anet-topo-layout,\n // so this only sets the *initial* preference for first-\n // time users on a dense fleet. Threshold 20 picked to\n // align with the existing density-aware breakpoints (R98\n // tier flip; R109 dense-label gating at 16). */}\n setLayout('grid');\n }\n } catch {}\n // sessions.length intentionally NOT in deps — this runs once on\n // mount and shouldn't re-fire when nodes join/leave (which would\n // override a user's mid-session toggle). The Ring|Grid chrome\n // button remains the authoritative source post-mount.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n // Round 170 / Loop: layout toggle (Ring ↔ Grid) used to teleport\n // every node from its old position to its new one in one paint\n // frame — the most jarring single user action left on the\n // canvas. Solution: dim the viewport <g> to ~45% opacity for\n // the duration of the swap so the eye reads it as a soft\n // crossfade-blink rather than a hard teleport. layoutSwitching\n // is a one-shot flag; the inline style on the viewport <g>\n // reads it and lerps opacity 1 → 0.45 → 1 across the swap.\n // Auto-clears after 400ms (covers fade-down 250ms + buffer for\n // React to commit the new layout's positions). Same pattern as\n // R168's smoothView arming but on a different visual axis\n // (opacity, not transform).\n const [layoutSwitching, setLayoutSwitching] = useState(false);\n const toggleLayout = () => {\n setLayoutSwitching(true);\n setTimeout(() => setLayoutSwitching(false), 400);\n setLayout(prev => {\n const next = prev === 'ring' ? 'grid' : 'ring';\n try { localStorage.setItem('anet-topo-layout', next); } catch {}\n return next;\n });\n };\n // Issue #113: node size scale (Vincent 4727 — nodes too big / crowded at\n // ~22 nodes). S/M/L → 0.7/0.84/1.0; default M (one notch down from the old\n // fixed size). Persisted like the layout toggle.\n const [nodeScale, setNodeScale] = useState(0.84);\n useEffect(() => {\n try {\n const saved = parseFloat(localStorage.getItem('anet-topo-nodescale') || '');\n if (saved === 0.7 || saved === 0.84 || saved === 1) setNodeScale(saved);\n } catch {}\n }, []);\n // Round 171 / Loop: nodeSize change picks up the R170 crossfade\n // pattern. Clicking S/M/L in the chrome re-derives every node's\n // radius + label sizing + (in grid layout) cell spacing — a\n // wholesale visual shift. Pre-R171 the resize snapped in one\n // paint frame; with this flag the viewport <g> opacity dims to\n // 0.45 for ~400ms, masking the redraw as a soft blink (same\n // vocabulary R170 uses for layout toggle). Bails early when\n // the picked scale matches the current one — clicking the\n // already-active button shouldn't fire a fade. Composes with\n // R170 layoutSwitching via `||` in the opacity expression; both\n // flags drive the same opacity transition but expose distinct\n // data-* attributes so tests can disambiguate which gesture\n // armed the crossfade.\n const [nodeSizeSwitching, setNodeSizeSwitching] = useState(false);\n const pickNodeScale = (v: number) => {\n if (v === nodeScale) return;\n setNodeSizeSwitching(true);\n setTimeout(() => setNodeSizeSwitching(false), 400);\n setNodeScale(v);\n try { localStorage.setItem('anet-topo-nodescale', String(v)); } catch {}\n };\n\n useEffect(() => {\n const fetchMessages = async () => {\n try {\n const res = await fetch('/api/hub/messages?limit=50');\n if (res.status === 401) {\n window.location.assign('/login');\n return;\n }\n\n const data = await res.json();\n setMessages(data.messages || []);\n } catch {}\n };\n fetchMessages();\n const interval = setInterval(fetchMessages, 5000);\n return () => clearInterval(interval);\n }, []);\n\n const {\n onlineNodes,\n offlineNodes,\n nodePositions,\n flowLinks,\n activeAliases,\n groupKeys,\n groupBoxes,\n gridContentBottom,\n } = useMemo(() => {\n const sseCount = (s: { alias: string; network_id?: string }) =>\n (s.network_id ? sseSessions[`${s.network_id}:${s.alias}`] : undefined) ?? sseSessions[s.alias];\n // Round 106 (issue #83): sort by alias so same-prefix agents\n // (通信龙 / 通信牛 / 通信工程马 …, or 研究员1号 / 研究员2号 …) end up\n // adjacent in the array — the tier layout below assigns angles by\n // index, so contiguous-in-array becomes contiguous-in-ring, i.e.\n // each team visually clusters. localeCompare keeps CJK ordering sane.\n const byAlias = (a: Session, b: Session) => a.alias.localeCompare(b.alias);\n // #112 umbrella: ghost age-out — an offline node not seen recently is\n // almost certainly a deleted node the server `/api/status` still\n // returns (#74 root cause is server-side; this is the dashboard-side\n // fallback so stale ghosts stop cluttering the topology). A missing\n // last_seen_at is kept (conservative — could be a legitimately new node).\n // Round 27 / P0: the 24h threshold was too lenient — Vincent's preview.29\n // screenshot showed B站马 nodes deleted ~4 h earlier still visible. A\n // healthy agent heartbeats every few seconds; if it's been silent for\n // an hour it's effectively gone. 1 h gives a fresh disconnect time to\n // come back while removing dead nodes well within an operator session.\n const GHOST_MS = 60 * 60 * 1000;\n const now = Date.now();\n const isGhost = (s: Session) => {\n if (s.status !== 'offline' || sseCount(s) || !s.last_seen_at) return false;\n // Round 35 / Loop: parseHubTime normalises SQL-style timestamps to UTC\n // before parsing so non-UTC browsers don't see a phantom 8-hour skew\n // and ghost freshly-disconnected nodes.\n const t = parseHubTime(s.last_seen_at);\n return t !== null && now - t > GHOST_MS;\n };\n const online = sessions.filter(s => s.status !== 'offline' || sseCount(s)).sort(byAlias);\n const offline = sessions.filter(s => s.status === 'offline' && !sseCount(s) && !isGhost(s)).sort(byAlias);\n const positions: Record<string, Point> = {};\n\n if (layout === 'grid') {\n // Issue #87 + #111: group-banded grid. Nodes are sorted by alias so\n // same-prefix aliases are adjacent; each multi-member prefix group then\n // gets its OWN row(s) starting at column 0, while singletons pack into\n // shared rows. Group-banded placement keeps every group's bounding box\n // (#111) a clean rectangle — no box ever overlaps another group's.\n const all = [...online, ...offline];\n const groupKeys = computeGroups(all);\n const cols = Math.max(1, Math.ceil(Math.sqrt(all.length)));\n // #112: gy0 starts below the (now compact) recent-signal / legend\n // overlay panels — their max bottom edge is y≈112 — so grid row 0 is\n // never occluded; gy1 extends near the canvas bottom for breathing room.\n const gx0 = 150, gx1 = 850, gy0 = 126, gy1 = 652;\n const cellW = (gx1 - gx0) / cols;\n // largest node radius at the current size scale — drives the group\n // label band + the row-height floor so nothing ever overlaps.\n const nodeR = Math.round(26 * nodeScale);\n\n // ordered runs of consecutive same-group-key nodes (≥2 = real group)\n const runs: { key: string; members: Session[] }[] = [];\n for (const s of all) {\n const gk = groupKeys[s.alias];\n const last = runs[runs.length - 1];\n if (last && last.key === gk) last.members.push(s);\n else runs.push({ key: gk, members: [s] });\n }\n\n // Pass 1 — assign each run to a band: a multi-member group owns its\n // rows (left-aligned, so its bounding box is a tidy rect); contiguous\n // singletons pack into shared rows (centred). Collect total row count.\n type Band = { members: Session[]; startRow: number; centred: boolean; isGroup: boolean };\n const bands: Band[] = [];\n let row = 0;\n let i = 0;\n while (i < runs.length) {\n if (runs[i].members.length >= 2) {\n bands.push({ members: runs[i].members, startRow: row, centred: false, isGroup: true });\n row += Math.ceil(runs[i].members.length / cols);\n i++;\n } else {\n const singles: Session[] = [];\n while (i < runs.length && runs[i].members.length < 2) {\n singles.push(runs[i].members[0]);\n i++;\n }\n bands.push({ members: singles, startRow: row, centred: true, isGroup: false });\n row += Math.ceil(singles.length / cols);\n }\n }\n const totalRows = Math.max(1, row);\n // #112: the group label sits in a band ABOVE the topmost node, so the\n // band must clear the node radius — GROUP_TOP is node-relative, never\n // cellH-derived (cellH-derived was the label↔node overlap bug Vincent\n // hit). cellH then floors at GROUP_TOP + 30 so the label band + bottom\n // padding always fit and stacked group boxes never touch; past the\n // floor the grid overflows and zoom/pan handles it.\n const GROUP_TOP = nodeR + 20;\n // Round 27 / P0 (Vincent screenshot preview.29): dense plain-text\n // node labels (`pos.y + radius + denseDrop`, with a 3 px containerBg\n // stroke halo for readability) at the BOTTOM row of band N would\n // paint OVER the start of band N+1's group label, creating the\n // \"blueleap → eleap\", \"agent-network-dashboard → t-network / board\"\n // visual chopping. Geometry of the collision:\n // dense label visual bottom = node_N.y + radius + denseDrop + halo\n // group label glyph top = boxY + 4 (text y=boxY+14, ~10px ascent)\n // = node_N+1.y - GROUP_TOP + 4\n // node_N+1.y - node_N.y = cellH for consecutive rows, so no-collide:\n // cellH ≥ radius + denseDrop + halo + GROUP_TOP - 4 + buffer\n // With halo=3 buffer=4: cellH ≥ nodeR + denseDrop + GROUP_TOP + 3.\n const denseDrop = nodeScale < 0.8 ? 12 : 14;\n const cellH = Math.max(\n 2 * nodeR + 22, // node + dense label within band\n GROUP_TOP + 12, // group-label band + box padding\n nodeR + denseDrop + GROUP_TOP + 3, // round-27 dense↔group label clearance\n Math.min(100, (gy1 - gy0) / totalRows),\n );\n\n // Pass 2 — place each band's members.\n for (const band of bands) {\n band.members.forEach((s, idx) => {\n const rowInBand = Math.floor(idx / cols);\n const c = idx % cols;\n const inRow = Math.min(cols, band.members.length - rowInBand * cols);\n const inset = band.centred ? ((cols - inRow) * cellW) / 2 : 0;\n positions[s.alias] = {\n x: gx0 + inset + (c + 0.5) * cellW,\n y: gy0 + (band.startRow + rowInBand + 0.5) * cellH,\n };\n });\n }\n\n const links = buildFlowLinks(messages, positions);\n const active = new Set<string>();\n links.forEach(link => { active.add(link.from); active.add(link.to); });\n // #111: one bounding box per multi-member group (Vincent 4722). Each\n // group owns its rows; GROUP_PAD fills the row space left below the\n // nodes after the label band, so stacked group boxes always have a\n // gap between them (GROUP_TOP is defined above, with cellH).\n const GROUP_PAD = Math.max(8, Math.min(26, cellH - GROUP_TOP - 8)); // side/bottom\n const groupBoxes = bands\n .filter(b => b.isGroup)\n .map(band => {\n const pts = band.members.map(s => positions[s.alias]).filter(Boolean);\n const xs = pts.map(p => p.x);\n const ys = pts.map(p => p.y);\n const minX = Math.min(...xs), minY = Math.min(...ys);\n // Round 58 / Loop: per-group status mix for the label pip strip.\n // Working = status==='working'. Idle = online but not working.\n // Offline = !isOnline (either status==='offline' AND no SSE, or\n // ghost-purged elsewhere — but ghosts never reach groupBoxes\n // since they're filtered out upstream). Counts feed the label\n // tspans directly so the strip stays inside the label's bbox,\n // preserving the node↔label overlap-test guarantee from R19.\n let w = 0, i = 0, o = 0;\n for (const s of band.members) {\n const isOn = s.status !== 'offline' || !!sseCount(s);\n if (s.status === 'working') w++;\n else if (isOn) i++;\n else o++;\n }\n return {\n key: band.members.length ? groupKeys[band.members[0].alias] : '',\n count: band.members.length,\n statuses: { working: w, idle: i, offline: o },\n x: minX - GROUP_PAD,\n y: minY - GROUP_TOP,\n w: Math.max(...xs) - minX + GROUP_PAD * 2,\n h: Math.max(...ys) - minY + GROUP_TOP + GROUP_PAD,\n };\n });\n // Round 28 / Loop: surface the grid's natural content bottom so the\n // mount effect can auto-fit zoom when the layout would overflow the\n // viewBox. = bottom of the last node row + its label drop + a small\n // breathing buffer. Round 27's cellH bump makes this overflow more\n // common (30-node fleets reach ~774 px, viewBox is 680).\n const gridContentBottom = gy0 + totalRows * cellH + 8;\n return {\n onlineNodes: online,\n offlineNodes: offline,\n nodePositions: positions,\n flowLinks: links,\n activeAliases: active,\n groupKeys,\n groupBoxes,\n gridContentBottom,\n };\n }\n\n // Round 97 (issue #50) + 98 (issue #61): three layout modes by N.\n // N ≤ 8 → single ring (r=220)\n // 8 < N ≤ 14 → two rings (inner r=175, outer r=260, half-step rot)\n // N > 14 → three rings (r=145/215/285, ⌈N/3⌉ per ring)\n // Each ring's spread is 1.78π so its node count drives chord length:\n // labels are 100px wide so each ring needs ≥110px chord per node.\n const tripleTier = online.length > onlineTripleThreshold;\n const dualTier = !tripleTier && online.length > onlineTierThreshold;\n let outerOnlineCount = online.length;\n if (tripleTier) {\n const per = Math.ceil(online.length / 3);\n const r1 = online.slice(0, per);\n const r2 = online.slice(per, 2 * per);\n const r3 = online.slice(2 * per);\n r1.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(r1.length, 1), onlineTripleInnerR);\n });\n const r1Spread = r1.length <= 2 ? Math.PI : Math.PI * 1.78;\n const r1Step = r1.length > 1 ? r1Spread / (r1.length - 1) : 0;\n r2.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(r2.length, 1), onlineTripleMidR, r1Step / 2);\n });\n r3.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(r3.length, 1), onlineTripleOuterR);\n });\n outerOnlineCount = r3.length;\n } else if (dualTier) {\n const innerCount = Math.ceil(online.length / 2);\n const outerCount = online.length - innerCount;\n const innerNodes = online.slice(0, innerCount);\n const outerNodes = online.slice(innerCount);\n innerNodes.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(innerCount, 1), onlineInnerRadius);\n });\n const innerSpread = innerCount <= 2 ? Math.PI : Math.PI * 1.78;\n const innerStep = innerCount > 1 ? innerSpread / (innerCount - 1) : 0;\n outerNodes.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(outerCount, 1), onlineOuterRadius, innerStep / 2);\n });\n outerOnlineCount = outerCount;\n } else {\n online.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(online.length, 1), onlineRadius);\n });\n }\n\n // Offset the offline ring radially by half the outermost online step so\n // offline bubbles sit in the angular gaps between online bubbles instead\n // of stacking directly behind them. Also push the outer ring further when\n // there are many offline nodes so labels don't crowd the legend.\n const outerSpreadBase = outerOnlineCount <= 2 ? Math.PI : Math.PI * 1.78;\n const outerStep = outerOnlineCount > 1 ? outerSpreadBase / (outerOnlineCount - 1) : 0;\n const offlineRotation = outerOnlineCount > 0 ? outerStep / 2 : 0;\n const offlineR = offlineRadius + Math.max(0, offline.length - 4) * 6;\n\n offline.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(offline.length, 1), offlineR, offlineRotation);\n });\n\n const links = buildFlowLinks(messages, positions);\n const active = new Set<string>();\n links.forEach(link => {\n active.add(link.from);\n active.add(link.to);\n });\n\n // Round 106 (issue #83): group key per alias → shared hue per team.\n const groupKeys = computeGroups([...online, ...offline]);\n\n return {\n onlineNodes: online,\n offlineNodes: offline,\n nodePositions: positions,\n flowLinks: links,\n activeAliases: active,\n groupKeys,\n // #111: group boxes are a grid-layout feature only — radially scattered\n // ring nodes can't be cleanly boxed. Ring keeps the #83 prefix hue.\n groupBoxes: [] as { key: string; count: number; statuses: { working: number; idle: number; offline: number }; x: number; y: number; w: number; h: number }[],\n // ring fits within VIEWBOX_H by construction (offlineRadius=325 + centre at y=330)\n gridContentBottom: 0,\n };\n }, [messages, sessions, sseSessions, layout, nodeScale]);\n\n const workingCount = onlineNodes.filter(s => s.status === 'working').length;\n // Round 6 / Loop: vendor distribution for the header chip — at a glance\n // \"what's in the fleet\" (A:5 M:2 O:8 书:12 …) without opening a node.\n // Sorted by count desc; \"unknown\" vendors collapse into a \"?\" bucket.\n const vendorDist = useMemo(() => {\n const tally = new Map<string, { initial: string; count: number; color: string }>();\n for (const s of [...onlineNodes, ...offlineNodes]) {\n const v = vendorForModel(s.model);\n const key = v.id === 'unknown' ? '?' : v.initial;\n const cur = tally.get(key);\n if (cur) cur.count++;\n else tally.set(key, { initial: key, count: 1, color: v.mono.text });\n }\n return [...tally.values()].sort((a, b) => b.count - a.count);\n }, [onlineNodes, offlineNodes]);\n // Round 109 (Vincent 4582 P0): hover-gated labels above this node count\n // so dense fleets show clean avatars instead of a wall of overlapping\n // label cards. 16 ≈ where the triple-tier rings start to crowd.\n const denseLayout = onlineNodes.length + offlineNodes.length > 16;\n const [hoveredAlias, setHoveredAlias] = useState<string | null>(null);\n // Round 86 / Loop: pointer-over-label preview. R63 wired the group\n // label to click-pin but hover gave no preview — the same hover/click\n // gap R83 closed for pressure-bar segments. This state lets the\n // label trigger the same dim mechanism a node hover already drives,\n // independently of whether the cursor happens to be over a member\n // node. ORs into hoveredGroup below so the existing derivation logic\n // (which composes with hoveredAlias, activeGroup, R85 marching ants)\n // keeps working unchanged.\n const [hoveredGroupLabel, setHoveredGroupLabel] = useState<string | null>(null);\n // Round 8 / Loop: which group is currently focused. Nodes outside this\n // group + other group boxes fade so the eye locks onto the team you're\n // pointing at. Singletons use their own alias as the group key.\n const hoveredGroup = hoveredGroupLabel\n ?? (hoveredAlias ? (groupKeys[hoveredAlias] ?? hoveredAlias) : null);\n // Round 63 / Loop: sticky group focus. R8 dims non-group nodes while\n // a member is hovered, but releasing the hover lets the focus fade.\n // Clicking the group label pins the group key here; activeGroup =\n // hoveredGroup ?? pinnedGroup so hover transiently overrides the\n // pin (handy for spot-comparing teams). Same compose pattern as\n // R60/R61 pinnedStatus.\n const [pinnedGroup, setPinnedGroup] = useState<string | null>(() => {\n // R66: same per-tab persistence treatment as pinnedStatus above.\n // The group key is opaque text (could be any prefix), so the init\n // reader can't validate it against a known enum — it just reads\n // whatever's stored. A useEffect below clears it if the value no\n // longer matches any current group (sessions changed since save).\n if (typeof window === 'undefined') return null;\n try { return sessionStorage.getItem('anet-topo-pinned-group'); } catch { return null; }\n });\n const activeGroup = hoveredGroup ?? pinnedGroup;\n // R66: sync pinnedGroup into sessionStorage when it changes; clear\n // it if it no longer matches any current group (the session set\n // can change between loads). The pinnedStatus sync sits next to\n // its declaration further down.\n useEffect(() => {\n try {\n if (pinnedGroup) sessionStorage.setItem('anet-topo-pinned-group', pinnedGroup);\n else sessionStorage.removeItem('anet-topo-pinned-group');\n } catch {}\n }, [pinnedGroup]);\n useEffect(() => {\n if (!pinnedGroup) return;\n const known = new Set(Object.values(groupKeys));\n if (!known.has(pinnedGroup)) setPinnedGroup(null);\n }, [pinnedGroup, groupKeys]);\n // Round 49 / Loop: reverse-direction of R40's edge-on-node-hover linkage.\n // R48 widened the flow hitbox to 16 px, so edges are precise enough to\n // serve as a state trigger. When the user hovers a flow edge, light up its\n // two endpoint nodes and dim the rest — \"who is this edge between\" becomes\n // visible without reading the tooltip. The set is the link's two aliases\n // (null when no edge hovered); node opacity composes this after inFocus.\n const [hoveredEdgeKey, setHoveredEdgeKey] = useState<string | null>(null);\n // Round 116 / Loop: sticky variant of hoveredEdgeKey. R56 made the\n // recent-signal rows brighten the matching edge on hover, but the\n // filter released on mouseleave — no way to lock a flow for a\n // closer look. Click-to-pin closes that gap, matching the\n // established hover→pin idiom (R60 status, R61 legend, R63 group,\n // R88 vendor). activeEdgeKey = hoveredEdgeKey ?? pinnedEdgeKey\n // below so hover still wins for spot-comparison while a pin is set.\n const [pinnedEdgeKey, setPinnedEdgeKey] = useState<string | null>(null);\n const activeEdgeKey = hoveredEdgeKey ?? pinnedEdgeKey;\n // Round 77 / Loop: hovering the \"N active links\" header chip globally\n // brightens every flow edge (1.5× opacity). Transient affordance — the\n // chip already says HOW MANY active flows exist; this answers WHERE\n // they are in one glance, without the user scanning the canvas for\n // moving particles. Reset on mouse leave.\n const [hoveredActiveLinks, setHoveredActiveLinks] = useState(false);\n // Round 115 / Loop: hub-center hover state. R52 made the hub\n // clickable (fit view) + cursor:pointer, but there was no\n // hover-time visual feedback — the click target felt guessed at\n // rather than confirmed. This state drives a subtle hint ring on\n // hover so users see the affordance before committing the click.\n const [hoveredHub, setHoveredHub] = useState(false);\n // R133: hover state for the recent-signal panel's \"+N more flows\"\n // navigation footer. Drives the on-hover opacity boost + underline\n // that signals interactivity, mirroring the hoveredHub idiom above.\n const [hoveredRecentMore, setHoveredRecentMore] = useState(false);\n // Round 346 / Loop: minimap-container hover affordance. The minimap\n // is a click-target (role=button at line ~7810, recenter-on-click +\n // Enter→resetView) but pre-R346 nothing visually changed on hover —\n // the only hint was the `cursor: crosshair` style. R346 lifts the\n // viewport rect (strokeWidth 1.5 → 1.75 + opacity 0.9 → 1.0) when\n // the user enters the minimap, marking \"this is the recenter target\n // and it's alive\". Sibling polish to the R332 minimap rounded-md →\n // rounded-lg corner family — that round refined geometry, this one\n // gives the viewport indicator inside the geometry a hover state.\n // 280ms ease-out transition list matches R199 smoothView vocabulary\n // so the visual joins the existing rhythm on the same rect.\n const [hoveredMinimap, setHoveredMinimap] = useState(false);\n // R135: panel-wide hover-elevation. The recent-signal + legend\n // panels both already host clickable rows (R56/R116 recent rows,\n // R55/R61 legend rows) and a clickable footer (R133), so the\n // chrome itself is interactive territory. Drop-shadow boost on\n // mouseenter says \"this whole panel is alive\" — matches the R18\n // KPI-card-hover idiom from the Overview page. Single state for\n // both panels since they don't overlap; null when neither hovered.\n const [hoveredPanel, setHoveredPanel] = useState<'recent' | 'legend' | null>(null);\n // Round 80 / Loop: vendor-letter hover in the distribution chip. The\n // chip already names vendor mix (`C:5 G:3 ?:1`); hover a letter and\n // every node from OTHER vendors dims. Surfaces the breakdown spatially\n // without inventing a new pin slot. Stores the vendor `initial`\n // (single char or \"?\") that matches the tally key.\n const [hoveredVendor, setHoveredVendor] = useState<string | null>(null);\n // Round 88 / Loop: vendor filter pin. R80 added hover-to-dim on the\n // vendor letters but the filter released as soon as the cursor left\n // — vendor was the only filter dimension without a sticky variant\n // (R60 status, R63 group, R69 Cmd+K all support pin). This state\n // closes the gap. Same pattern as pinnedStatus / pinnedGroup:\n // per-tab sessionStorage persistence, Esc clears, activeVendor =\n // hoveredVendor ?? pinnedVendor so hover still wins for spot-\n // comparison while a pin is active.\n const [pinnedVendor, setPinnedVendor] = useState<string | null>(() => {\n if (typeof window === 'undefined') return null;\n try { return sessionStorage.getItem('anet-topo-pinned-vendor'); } catch { return null; }\n });\n const activeVendor = hoveredVendor ?? pinnedVendor;\n useEffect(() => {\n try {\n if (pinnedVendor) sessionStorage.setItem('anet-topo-pinned-vendor', pinnedVendor);\n else sessionStorage.removeItem('anet-topo-pinned-vendor');\n } catch {}\n }, [pinnedVendor]);\n // R89: stale-purge. If the fleet's vendor distribution changes such\n // that a previously-pinned vendor is gone (last node of that\n // vendor disconnected), clear the pin so the chip row doesn't\n // show \"filter: A · 0\" forever. Matches the same defensive purge\n // pattern pinnedGroup uses higher up — sessionStorage survives\n // reloads, but a stored value that no longer matches reality\n // would paint with an impossible filter.\n useEffect(() => {\n if (pinnedVendor && !vendorDist.some(v => v.initial === pinnedVendor)) {\n setPinnedVendor(null);\n }\n }, [pinnedVendor, vendorDist]);\n // Round 55 / Loop: hovering a legend status row dims nodes whose status\n // doesn't match. The legend was passive — \"what does this colour mean\".\n // Now it answers \"show me all of these\" the same way R8 group-focus\n // answers \"show me this team\". Three values match the legend rows.\n const [hoveredStatus, setHoveredStatus] = useState<'working' | 'idle' | 'offline' | null>(null);\n // Round 60 / Loop: sticky variant of `hoveredStatus`. R55 only filters\n // while the user is actively hovering the legend; for sweeping a fleet\n // you want to LOCK the filter. Each segment of the R31 pressure bar\n // toggles a pin — click again on the same segment to release. The node\n // opacity formula reads `activeStatus = hoveredStatus ?? pinnedStatus`\n // so hover transiently overrides a pin (handy for spot-comparison)\n // without nuking it.\n // Round 66 / Loop: pin survives a page reload via sessionStorage —\n // per-tab not per-browser (a new tab starts clean, intentionally).\n // The init reader validates against the known status set so a stale\n // / corrupt value can't paint the canvas with an impossible filter.\n const [pinnedStatus, setPinnedStatus] = useState<'working' | 'idle' | 'offline' | null>(() => {\n if (typeof window === 'undefined') return null;\n try {\n const v = sessionStorage.getItem('anet-topo-pinned-status');\n return (v === 'working' || v === 'idle' || v === 'offline') ? v : null;\n } catch { return null; }\n });\n const activeStatus = hoveredStatus ?? pinnedStatus;\n // R66: sync pinnedStatus into sessionStorage when it changes. Paired\n // with the matching effect for pinnedGroup higher up.\n useEffect(() => {\n try {\n if (pinnedStatus) sessionStorage.setItem('anet-topo-pinned-status', pinnedStatus);\n else sessionStorage.removeItem('anet-topo-pinned-status');\n } catch {}\n }, [pinnedStatus]);\n // R69: listen for Cmd+K palette pin actions. The palette can't reach\n // this component's state directly so it dispatches a CustomEvent;\n // we react by setting pinnedStatus / pinnedGroup. The palette also\n // writes to sessionStorage in lockstep, so the R66 init readers\n // would pick it up on reload — the event handler is what keeps the\n // currently-mounted canvas in sync without one.\n useEffect(() => {\n const onPin = (e: Event) => {\n const detail = (e as CustomEvent).detail || {};\n if (detail.kind === 'clear') {\n // R90: extend the universal clear to vendor. R69 only cleared\n // status + group because pinnedVendor didn't exist yet; R88\n // shipped the third pin and R90 closes the palette gap so\n // \"Clear topology filters\" really means all of them.\n // R117: extend again to pinnedEdgeKey — R116 added the 4th\n // pin dim and this command must clear it too or the edge stays\n // bright in the canvas while every other pill clears.\n setPinnedStatus(null);\n setPinnedGroup(null);\n setPinnedVendor(null);\n setPinnedEdgeKey(null);\n } else if (detail.kind === 'clear-vendor') {\n // R90: granular vendor-only clear so power users can release\n // just the vendor without disturbing status/group pins.\n setPinnedVendor(null);\n } else if (detail.kind === 'clear-edge') {\n // R117: granular edge-only clear, mirror of R90 clear-vendor.\n setPinnedEdgeKey(null);\n } else if (detail.kind === 'status') {\n const v = detail.value;\n if (v === 'working' || v === 'idle' || v === 'offline') setPinnedStatus(v);\n } else if (detail.kind === 'group' && typeof detail.value === 'string') {\n setPinnedGroup(detail.value);\n } else if (detail.kind === 'vendor' && typeof detail.value === 'string') {\n // R108: palette pin-vendor support. R69 added pin-status, R88\n // added clickable vendor letters in the chip row, R90 added\n // granular clear-vendor — but the palette never gained a\n // PIN-vendor command. This branch listens for it; the matching\n // commands live in CommandPalette R108. The stale-purge\n // useEffect higher up already drops a pin whose vendor isn't\n // in the current distribution, so commands for an absent\n // vendor are harmless.\n setPinnedVendor(detail.value);\n }\n };\n window.addEventListener('anet:topo-pin', onPin);\n return () => window.removeEventListener('anet:topo-pin', onPin);\n }, []);\n // R74 listener for layout + view palette commands lives below\n // fitView's declaration — see further down in the file.\n const hoveredEdgeEndpoints = useMemo<Set<string> | null>(() => {\n // R116: compose hover ?? pin so pinning a row via click keeps the\n // endpoint ring + edge ladder lit after mouseleave.\n if (!activeEdgeKey) return null;\n const link = flowLinks.find(l => l.key === activeEdgeKey);\n return link ? new Set([link.from, link.to]) : null;\n }, [activeEdgeKey, flowLinks]);\n\n // --- Round 103 (issue #81): fullscreen + zoom + pan interaction layer ---\n // DIY native (no d3 / svg-pan-zoom): wrap the topology content in a single\n // <g transform> and drive it with wheel + pointer-drag. The panel <rect>\n // backdrop stays fixed so panning never reveals empty canvas. View state\n // {zoom,x,y} persists to localStorage (same sticky pattern as brand flag).\n const VIEWBOX_W = 1000;\n const VIEWBOX_H = 680;\n const ZOOM_MIN = 0.5;\n const ZOOM_MAX = 4;\n const containerRef = useRef<HTMLDivElement>(null);\n const svgRef = useRef<SVGSVGElement>(null);\n const [view, setView] = useState({ zoom: 1, x: 0, y: 0 });\n const viewRef = useRef(view);\n const dragRef = useRef({ active: false, startX: 0, startY: 0, baseX: 0, baseY: 0 });\n const [isFullscreen, setIsFullscreen] = useState(false);\n // Round 184 / Loop: chrome reset button gets a one-shot icon spin\n // on click. resetSpinning is armed for 450ms (matches the CSS\n // animation duration in globals.css `.anet-reset-spin`); the SVG\n // icon picks up the class while armed. Same arming pattern as\n // R168/R169/R170/R171 smoothView flags but driving a CSS animation\n // instead of a CSS transition. prefers-reduced-motion blanket\n // override in R29 globals.css neutralises animation-duration\n // universally so the spin completes in 1ms when reduced motion is\n // requested.\n const [resetSpinning, setResetSpinning] = useState(false);\n const armResetSpin = () => {\n setResetSpinning(true);\n setTimeout(() => setResetSpinning(false), 460);\n };\n // Round 186 / Loop: chrome zoom-in / zoom-out buttons get a brief\n // icon pop on click — same click-feel idiom R184 added for the\n // reset spin, but a scale pulse rather than a rotation since +/−\n // icons rotating wouldn't read semantically. Tracks which button\n // is currently popping so only that icon picks up the class.\n // Arms for 240ms (CSS animation 220ms + 20ms buffer) so a quick\n // re-click can replay cleanly.\n // Round 249 / Loop: extend chromePopping to cover ring/grid layout\n // toggle + fullscreen button alongside the existing zoom-in/zoom-out.\n // Pre-R249 only the zoom buttons fired the R186 .anet-chrome-pop scale\n // pulse on click; the other chrome controls (layout toggle, fullscreen)\n // had no transient \"I just clicked\" signal — silent click → state change\n // with only the post-click visual difference to confirm action. R249\n // gives every clickable chrome control the same 220ms scale pulse, so\n // the whole strip speaks one consistent click vocabulary. Reset button\n // keeps its own R184 rotation animation (different gesture, semantic).\n // Node-size S/M/L keep their R171 layoutSwitching crossfade (already\n // gestural). Type union grows but the helper signature stays one-arg.\n type ChromePop = 'zoom-in' | 'zoom-out' | 'layout-ring' | 'layout-grid' | 'fullscreen' | 'size-S' | 'size-M' | 'size-L';\n const [chromePopping, setChromePopping] = useState<ChromePop | null>(null);\n const popChrome = (which: ChromePop) => {\n setChromePopping(which);\n setTimeout(() => setChromePopping(prev => prev === which ? null : prev), 240);\n };\n // Issue #100: singleton chat popover. One alias at a time — clicking\n // another node swaps the target and the conversation switches in place.\n const [chatAlias, setChatAlias] = useState<string | null>(null);\n // Round 14 / Loop: one-shot click ripple — fades outward from the clicked\n // node so the click registers physically before the popover snaps in.\n // Pairs with the Round 11 chat-focus ring: ripple expands, ring locks on.\n // Keyed by ts so re-clicking the same node remounts the <circle> and the\n // SMIL <animate> replays. Cleared 600ms after click (longer than the\n // 500ms animation so a final frame at opacity 0 is still in the tree).\n const [clickRipple, setClickRipple] = useState<{\n ts: number; x: number; y: number; r0: number; color: string;\n } | null>(null);\n // #84: when a node is renamed while its chat popover is open, follow the\n // rename so the conversation keeps targeting a live alias.\n useEffect(() => {\n if (renameSignal && renameSignal.from === chatAlias) {\n setChatAlias(renameSignal.to);\n }\n // chatAlias intentionally omitted — only react to a new rename signal,\n // not to the user opening/closing the popover.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [renameSignal]);\n\n useEffect(() => { viewRef.current = view; }, [view]);\n\n // restore persisted view once on mount\n useEffect(() => {\n try {\n const raw = localStorage.getItem('anet-topo-view');\n if (raw) {\n const v = JSON.parse(raw);\n if (typeof v?.zoom === 'number') {\n setView({\n zoom: Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, v.zoom)),\n x: typeof v.x === 'number' ? v.x : 0,\n y: typeof v.y === 'number' ? v.y : 0,\n });\n }\n }\n } catch {}\n }, []);\n\n // Round 28 / Loop: first-paint auto-fit. Round 27's cellH bump means\n // dense grids (≥6-7 rows) overflow the 680-px viewBox at the natural\n // 100% zoom — a new user sees the topology with its bottom rows\n // clipped and no hint that there's more below. Auto-fit on first\n // paint (and only if the user has no persisted view) sets the\n // initial zoom so all content fits. Subsequent zoom changes persist\n // and override the auto-fit on reload; explicit reset (0 key) still\n // goes back to 100% so the gesture's \"reset to natural size\" semantic\n // is preserved.\n //\n // Capture pre-mount persistence in a useState initializer — the\n // existing `persist` effect (declared below) runs on first render\n // with the default {1,0,0} view and writes it to localStorage before\n // the auto-fit effect (deps on async-arriving sessions) gets a turn.\n // Reading localStorage from the effect would see that write and\n // skip the fit. The useState snapshot fires once, before any effects.\n const [hadPersistedViewOnMount] = useState<boolean>(\n () => typeof window !== 'undefined' && !!localStorage.getItem('anet-topo-view'),\n );\n const autoFitDoneRef = useRef(false);\n useEffect(() => {\n if (autoFitDoneRef.current) return;\n if (hadPersistedViewOnMount) {\n autoFitDoneRef.current = true;\n return;\n }\n if (layout !== 'grid' || sessions.length === 0 || !gridContentBottom) return;\n if (gridContentBottom <= VIEWBOX_H) {\n autoFitDoneRef.current = true; // no overflow → no fit needed\n return;\n }\n const fitZoom = Math.max(ZOOM_MIN, Math.min(1, VIEWBOX_H / gridContentBottom));\n setView({ zoom: fitZoom, x: 0, y: 0 });\n autoFitDoneRef.current = true;\n }, [layout, sessions.length, gridContentBottom, hadPersistedViewOnMount]);\n\n // persist view\n useEffect(() => {\n try { localStorage.setItem('anet-topo-view', JSON.stringify(view)); } catch {}\n }, [view]);\n\n // track fullscreen state (button label + Esc-exit sync)\n useEffect(() => {\n const onFsChange = () => setIsFullscreen(document.fullscreenElement === containerRef.current);\n document.addEventListener('fullscreenchange', onFsChange);\n return () => document.removeEventListener('fullscreenchange', onFsChange);\n }, []);\n\n // wheel zoom — native non-passive listener so preventDefault() actually\n // stops the page from scrolling. Uses functional setState so the listener\n // never goes stale and can attach once.\n useEffect(() => {\n const svg = svgRef.current;\n if (!svg) return;\n const onWheel = (e: WheelEvent) => {\n // Round 23 / Loop: only zoom on Ctrl/Meta+wheel when inline; plain\n // wheel over the canvas keeps scrolling the page (the topo sits at\n // the top of /; trapping page scroll mid-page is the classic\n // \"scroll-jail\" anti-pattern). In fullscreen the canvas owns the\n // viewport so any wheel zooms — no page scroll to preserve. Pinch-\n // zoom on a trackpad surfaces as ctrlKey=true natively, which is\n // also what we want (zoom the canvas, not the browser).\n if (!isFullscreen && !e.ctrlKey && !e.metaKey) return;\n e.preventDefault();\n const rect = svg.getBoundingClientRect();\n const mx = ((e.clientX - rect.left) / rect.width) * VIEWBOX_W;\n const my = ((e.clientY - rect.top) / rect.height) * VIEWBOX_H;\n setView(prev => {\n // Round 104 (issue #81 follow-up): Vincent 实测 zoom 太灵敏. The\n // old factor was a fixed 1.15x per wheel *event* — fine for a\n // mouse notch but a trackpad fires dozens of events per gesture\n // so it compounded into huge jumps. Scale the factor by deltaY\n // magnitude with a small coefficient, then clamp the per-event\n // change so no single tick moves more than ~8%.\n const factor = Math.min(1.08, Math.max(0.926, Math.exp(-e.deltaY * 0.0006)));\n const nz = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, prev.zoom * factor));\n const ratio = nz / prev.zoom;\n // keep the point under the cursor stationary\n return { zoom: nz, x: mx - (mx - prev.x) * ratio, y: my - (my - prev.y) * ratio };\n });\n };\n svg.addEventListener('wheel', onWheel, { passive: false });\n return () => svg.removeEventListener('wheel', onWheel);\n // Re-attach when the fullscreen flag toggles so the handler's\n // closure picks up the new value (capture-by-closure means we'd\n // otherwise read the stale boolean forever).\n }, [isFullscreen]);\n\n // Round 21 / Loop: pan-aware cursor. Static `grab` gave no tactile cue\n // that the canvas was actually being dragged — \"grabbing\" while\n // dragRef.current.active = true tells the hand it's moving the\n // viewport. Mirroring dragRef.active into state is the only way to\n // re-render the SVG style; the ref itself doesn't trigger renders.\n const [isPanning, setIsPanning] = useState(false);\n const onPointerDown = (e: React.PointerEvent<SVGSVGElement>) => {\n if (e.button !== 0) return;\n (e.currentTarget as Element).setPointerCapture?.(e.pointerId);\n dragRef.current = {\n active: true,\n startX: e.clientX,\n startY: e.clientY,\n baseX: viewRef.current.x,\n baseY: viewRef.current.y,\n };\n setIsPanning(true);\n };\n const onPointerMove = (e: React.PointerEvent<SVGSVGElement>) => {\n const d = dragRef.current;\n if (!d.active) return;\n const svg = svgRef.current;\n if (!svg) return;\n const rect = svg.getBoundingClientRect();\n const dx = ((e.clientX - d.startX) / rect.width) * VIEWBOX_W;\n const dy = ((e.clientY - d.startY) / rect.height) * VIEWBOX_H;\n setView(prev => ({ ...prev, x: d.baseX + dx, y: d.baseY + dy }));\n };\n const onPointerUp = (e: React.PointerEvent<SVGSVGElement>) => {\n if (!dragRef.current.active) return;\n dragRef.current.active = false;\n setIsPanning(false);\n try {\n (e.currentTarget as Element).releasePointerCapture?.(e.pointerId);\n } catch {}\n };\n\n // zoom buttons — zoom around the canvas center\n const zoomBy = (factor: number) => {\n setView(prev => {\n const nz = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, prev.zoom * factor));\n const ratio = nz / prev.zoom;\n const cx0 = VIEWBOX_W / 2;\n const cy0 = VIEWBOX_H / 2;\n return { zoom: nz, x: cx0 - (cx0 - prev.x) * ratio, y: cy0 - (cy0 - prev.y) * ratio };\n });\n };\n // Round 169 / Loop: discrete-zoom wrapper that arms the R168\n // smoothView flag before invoking zoomBy. Keyboard + / − and\n // the chrome zoom buttons fire once per gesture, so each\n // 1.2× step deserves the same 300ms glide R168 gives\n // reset/fit. Wheel zoom keeps calling zoomBy() directly —\n // every tick should respond live without lag. The arming\n // path is identical to resetView/fitView so all four\n // discrete-zoom surfaces share one timing constant.\n const zoomByDiscrete = (factor: number) => {\n setSmoothView(true);\n setTimeout(() => setSmoothView(false), 350);\n zoomBy(factor);\n };\n // Round 168 / Loop: smoothView is a one-shot flag that arms a\n // CSS transition on the viewport <g> transform attribute. Set\n // true when resetView/fitView fires; the inline style on the\n // viewport <g> reads this flag and conditionally applies\n // `transition: transform 300ms ease-out`. Auto-clears after\n // 350ms (just past the transition end) via setTimeout so\n // subsequent pan/wheel zoom stays snappy with no lag. Keeping\n // it as state (vs ref) so the React rerender re-applies the\n // style attribute synchronously when the transform also\n // changes — both attribute mutation and transition class\n // arrive in the same paint frame, which is what triggers the\n // browser to animate between the old and new transform values.\n const [smoothView, setSmoothView] = useState(false);\n const armSmoothView = () => {\n setSmoothView(true);\n setTimeout(() => setSmoothView(false), 350);\n };\n const resetView = () => {\n armSmoothView();\n setView({ zoom: 1, x: 0, y: 0 });\n };\n\n // Round 29 / Loop: `f` = fit-to-content. Shared by the Round 28\n // first-paint auto-fit effect and the keyboard handler so the math is\n // in one place. When content already fits at natural zoom, this is\n // effectively a \"recenter\" — `f` always lands on a known good view.\n // R168: arm smoothView so the transition glides instead of snapping\n // when the operator invokes fit-to-content via the hub click (R52),\n // chrome button, `f` key, or palette command.\n const fitView = useCallback(() => {\n const zoom = !gridContentBottom || gridContentBottom <= VIEWBOX_H\n ? 1\n : Math.max(ZOOM_MIN, Math.min(1, VIEWBOX_H / gridContentBottom));\n setSmoothView(true);\n setTimeout(() => setSmoothView(false), 350);\n setView({ zoom, x: 0, y: 0 });\n }, [gridContentBottom]);\n\n // R74: listen for layout + view palette commands. Sister to R69's\n // pin listener — palette dispatches a CustomEvent, the reducer here\n // calls toggleLayout / fitView. Sits below fitView's declaration so\n // the deps list resolves cleanly.\n useEffect(() => {\n const onLayout = (e: Event) => {\n const detail = (e as CustomEvent).detail || {};\n if (detail.kind === 'toggle') toggleLayout();\n };\n const onView = (e: Event) => {\n const detail = (e as CustomEvent).detail || {};\n if (detail.kind === 'fit') fitView();\n };\n window.addEventListener('anet:topo-layout', onLayout);\n window.addEventListener('anet:topo-view', onView);\n return () => {\n window.removeEventListener('anet:topo-layout', onLayout);\n window.removeEventListener('anet:topo-view', onView);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [fitView]);\n\n // Round 22 / Loop: keyboard zoom — +/= zoom in, - zoom out, 0 reset.\n // Round 29 / Loop: +f to fit content.\n // Listen on window so the user doesn't need to focus the SVG first,\n // but only act when no text input has focus (otherwise typing \"-\" in\n // a chat would zoom the topology — surprising and bad). Modifier keys\n // pass through so Cmd/Ctrl combos still hit their owners. The button\n // titles below document these shortcuts so users discover them.\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.ctrlKey || e.metaKey || e.altKey) return;\n const ae = document.activeElement as HTMLElement | null;\n if (ae) {\n const tag = ae.tagName;\n if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || ae.isContentEditable) return;\n }\n if (e.key === '+' || e.key === '=') { zoomByDiscrete(1.2); e.preventDefault(); }\n else if (e.key === '-' || e.key === '_') { zoomByDiscrete(1 / 1.2); e.preventDefault(); }\n else if (e.key === '0') { resetView(); e.preventDefault(); }\n else if (e.key === 'f' || e.key === 'F') { fitView(); e.preventDefault(); }\n // Round 32 / Loop: `l` toggles ring|grid. The vim-style `g l` route\n // (Audit Log) requires a preceding `g` within 1500ms; a bare `l`\n // outside that window is free for topology use.\n else if (e.key === 'l' || e.key === 'L') { toggleLayout(); e.preventDefault(); }\n // Round 62 / Loop: Esc clears the R60/R61 pinned status filter so\n // users have a universal-cancel keyboard out. Esc on an open chat\n // is owned by ChatPopover (which only mounts when chatAlias is\n // set), so this handler is effectively scoped to \"no chat open\".\n // We additionally guard on chatAlias to be explicit — if the chat\n // closed mid-cycle, the pin can still be cleared on the next Esc.\n // R63: extends to pinnedGroup too. Clears WHATEVER pin is active\n // (one Esc collapses all topology pins) so the keyboard escape\n // route stays a single key, not \"Esc maybe Esc again\".\n else if (e.key === 'Escape' && !chatAlias && (pinnedStatus || pinnedGroup || pinnedVendor || pinnedEdgeKey)) {\n // R88/R116: extend the universal-cancel to vendor + edge too.\n // One Esc collapses every topology pin (matches R62/R63's\n // \"single key, not Esc-maybe-Esc-again\" promise).\n if (pinnedStatus) setPinnedStatus(null);\n if (pinnedGroup) setPinnedGroup(null);\n if (pinnedVendor) setPinnedVendor(null);\n if (pinnedEdgeKey) setPinnedEdgeKey(null);\n e.preventDefault();\n }\n };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n // zoomBy / resetView are stable wrt setView callback; fitView changes\n // with gridContentBottom so deps list catches it. R62 adds chatAlias\n // and pinnedStatus so the Escape branch reads fresh state (re-binding\n // the listener on these state changes is sub-ms — cheaper than refs).\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [fitView, chatAlias, pinnedStatus, pinnedGroup, pinnedVendor, pinnedEdgeKey]);\n\n const toggleFullscreen = () => {\n const el = containerRef.current;\n if (!el) return;\n if (document.fullscreenElement) {\n document.exitFullscreen?.();\n } else {\n const req =\n el.requestFullscreen ||\n (el as unknown as { webkitRequestFullscreen?: () => void }).webkitRequestFullscreen;\n req?.call(el);\n }\n };\n\n return (\n <section className=\"w-full max-w-6xl mx-auto mb-8\">\n {/* Round 299 / Loop: title block bottom margin mb-3 (12px) →\n mb-4 (16px). After R298 tightened the title-block internal\n gap (12→10px) packing brand-logo + kicker + h2 into a more\n cohesive editorial unit, the outer bottom margin to the\n topology canvas should breathe more — denser title block\n + tighter follow-on space read as cramped. Bumping the\n gap below the title block lets the canvas frame\n itself more clearly as the main visual subject. 16px is\n the conventional SaaS-product section-header-to-content\n baseline (Stripe / Linear / Vercel marketing). Geometry:\n adds 4 CSS px between title block bottom and topology\n frame top — small but cumulative with the R298 internal\n tighten, the title block reads as a *deliberate* badge\n rather than a casually-stacked label. */}\n {/* Round 334 / Loop: header outer wrapper mobile gap-3 → gap-2.5\n (12 px → 10 px). The wrapper is `flex flex-col` on narrow\n viewports (title-block above, chip-row below) — at mobile\n its vertical gap was 12 px while the title-block internal\n (R298) and chip-row internal (R328) both rhythm at 10 px.\n R334 unifies the OUTER vertical gap to 10 px so mobile\n stacked layout matches the established gap-rhythm tier\n (title-block 10 / chip-row 10 / chrome 8 — R298/R328/R326).\n Desktop `sm:flex-row` is unaffected: in row mode the gap-3\n would have applied horizontally but the wrapper relies on\n `sm:justify-between` for left/right anchoring (gap is then\n decorative only between the two flex-grow groups). Net\n mobile bump: 2 px tighter vertical breathing between\n title-block + chip-row. Geometry-safe — topo-overlap-test\n reads SVG-internal bbox, not header layout. */}\n <div\n className={`flex flex-col gap-2.5 sm:flex-row sm:items-end sm:justify-between mb-4 px-1${isFullscreen ? ' hidden' : ''}`}\n data-topo-header-row\n data-topo-header-hidden={isFullscreen ? 'true' : 'false'}\n >\n {/* Round 267 / Loop: title block adopts leading-tight on both\n kicker and h2 for a tighter editorial-style rhythm. Pre-\n R267 the kicker used Tailwind's compound `text-xs` (line-\n height 16px = 1.33 ratio) and the h2 used `text-lg` (line-\n height 28px = 1.56) — adequate but loose for a kicker→\n title sequence. R267 applies `leading-tight` (1.25) to\n both, shrinking effective line-heights to 15px + 22.5px =\n 37.5px total title block height (vs 44px pre-R267 → ~15%\n more compact) while preserving the cap-top to descender\n visual proportions. Result: kicker and title read as a\n single typographic unit rather than two loosely-stacked\n lines. data-topo-section-kicker / data-topo-section-title\n attrs make both probe-able. */}\n {/* P0 (Vincent 5222 / 通信龙 R278 dispatch): integrate sleep2agi\n brand logo into the title-block.\n Why HTML title-block instead of SVG canvas: the SVG region\n has a high-frequency concurrent-editor edit race with codex\n (see project_dashboard_concurrent_editors). The title block\n is HTML-side, low edit traffic, and gives the brand mark\n first-glance presence above the topology canvas — the exact\n \"Twitter screenshot 一眼看出 sleep2agi\" outcome Vincent 5215\n asked for.\n Logo construction: inline SVG so currentColor inherits\n from the parent text-{color} class — theme-aware without\n an extra asset request. Same crescent geometry as\n public/sleep2agi-logo.svg. 36×36 px so it's clearly\n readable at a16:9 Twitter crop, paired with the kicker +\n h2 typography via flex layout. text-cyan-300 in cyber +\n text-emerald-600 in light keeps the moon brand-aligned\n with the canvas accent palette. */}\n {/* Round 298 / Loop: title-block gap-3 (12px) → gap-2.5 (10px).\n The R297 codex-bundle brand-logo at 36×36 paired with the\n R285 tracking-widest kicker + R286 tracking-tight h2 forms\n an editorial \"logo + title\" unit. At gap-3 the 12px between\n logo right-edge and text left-edge reads as two separate\n elements — visual spacing slightly outpaces the relationship\n density (logo IS the brand mark for the kicker's\n \"Network Topology\"). gap-2.5 (10px) is the tight-pack\n convention SaaS-product header logos use (Stripe / Vercel /\n Linear top-nav logo + product name spacing), grouping logo\n + title as one read. Geometry: 36 + 10 + ~120 (title text\n width) = 166px total title-block width vs 168px pre-R298 —\n no measurable layout shift, just a deliberate tighter\n grouping. */}\n <div className=\"flex items-center gap-2.5\">\n {/* Round 297 / Loop: brand-logo color picks up the 200ms ease-\n out transition. Pre-R297 the moon glyph had theme-\n conditional color (cyber #67e8f9 cyan ↔ light #0d9488\n teal) but no transition declaration — flipping themes\n made the brand mark snap to its new color in one frame,\n jarring against the surrounding R245/R246/R247/R253/R254\n family that smooths every neighbouring fill / stroke /\n filter at the same 200ms cadence. Adding the transition\n brings the brand mark into the coordinated theme-toggle\n choreography: title block + canvas + chrome all ease as\n one unit. CSS color transition is well supported on the\n `color` property (which currentColor inside the masked\n <rect> inherits), so no SMIL trick needed. */}\n {/* Round 316 / Loop: brand-logo width/height 36 → 40 for\n slightly stronger first-glance presence. After R298\n tightened the title-block flex gap (12→10px) and R299\n widened the bottom margin (12→16px), the logo can hold\n more visual weight to balance the kicker+h2 stack on\n its right. 40px is 11% larger by edge, ~23% by area —\n visible bump on a Twitter-screenshot crop without\n overpowering the h2 at text-lg/font-semibold (R286).\n viewBox 32×32 unchanged so the inner crescent geometry\n scales proportionally. */}\n <svg\n width=\"40\" height=\"40\" viewBox=\"0 0 32 32\" aria-hidden\n className=\"shrink-0\"\n data-topo-brand-logo\n style={{\n color: isLight ? '#0d9488' : '#67e8f9',\n transition: 'color 200ms ease-out',\n }}\n >\n <mask id=\"s2a-titleblock-moon-mask\">\n <rect width=\"32\" height=\"32\" fill=\"black\" />\n <circle cx=\"16\" cy=\"16\" r=\"13\" fill=\"white\" />\n <circle cx=\"20.5\" cy=\"14.5\" r=\"11\" fill=\"black\" />\n </mask>\n <rect width=\"32\" height=\"32\" fill=\"currentColor\" mask=\"url(#s2a-titleblock-moon-mask)\" />\n </svg>\n <div>\n {/* Round 285 / Loop: kicker tracking-wider → tracking-widest.\n An uppercase eyebrow label at text-xs benefits from\n wider letter-spacing — Tailwind's tracking-widest is\n 0.1em vs tracking-wider's 0.05em. At small caps,\n 0.1em is the conventional SaaS-eyebrow spacing (Stripe,\n Linear, Vercel marketing kicker style); 0.05em reads\n closer to body-text density. The widened spacing\n telegraphs \"this is a label, not a sentence\" without\n changing color or size, deepening the editorial\n hierarchy R267 set up between kicker and h2. */}\n {/* Round 296 / Loop: kicker text-gray-600 → text-gray-500\n for slightly better legibility on the dark cyber backdrop.\n gray-600 (#4b5563) read as a near-invisible label on\n cyber (the canvas + side rail are deeply dark); gray-500\n (#6b7280) lifts the eyebrow into the band where the eye\n registers it as a deliberate label vs swallowed text,\n while still sitting clearly below text-white h2 title\n in the visual hierarchy. Tailwind classes are theme-\n neutral so the bump applies to both themes; in light\n theme gray-500 is still appropriate as a muted-label\n shade on white bg. Hierarchy preserved: title-white >\n kicker-gray-500 (R285 tracking-widest still in place). */}\n {/* Round 300 / Loop (milestone): kicker picks up font-medium\n (500). Pre-R300 the eyebrow used default font-weight\n (400/normal). At text-xs (12px) + uppercase + R285\n tracking-widest, default-weight letters read slightly\n under-authored — uppercase at small sizes wants a touch\n more stroke weight to feel like a deliberate label.\n font-medium (500) is the conventional SaaS-eyebrow\n weight (Stripe / Vercel / Linear marketing kicker\n style — same family that informed R285's tracking-\n widest decision). Stays clearly below the h2's font-\n semibold (600) + larger size so hierarchy is preserved:\n h2 (text-lg/600) > kicker (text-xs/500/gray-500).\n R300 marks the milestone of 25 rounds (R275-R300) of\n continuous TopoGraph polish + codex's Vincent 5215/\n 5222 logo asset+integration work. */}\n <div className=\"text-xs uppercase text-gray-500 tracking-widest leading-tight font-medium\" data-topo-section-kicker>Network Topology</div>\n {/* Round 286 / Loop: title 'Command mesh' adopts tracking-tight\n (-0.025em) to complement R285 kicker tracking-widest. Wide\n eyebrow + tight headline is the conventional editorial\n pairing — Apple / Stripe / Vercel / Linear all use this\n dual-axis typographic rhythm. The kicker's 0.1em pushes\n letters APART (label feel); the headline's -0.025em pulls\n them TOGETHER (deliberate, designed-headline feel). At\n text-lg (18px) the shift is ~0.45px per gap — small but\n cumulatively legible across 12 characters. font-semibold\n (600) stays — tracking-tight does the heavy lifting for\n the editorial register. */}\n <h2 className=\"text-lg text-white font-semibold leading-tight tracking-tight\" data-topo-section-title>Command mesh</h2>\n </div>\n </div>\n {/* Round 328 / Loop: chip-row strip wrapper gap 2 → 2.5\n (8px → 10px between chips). Pre-R328 the inter-chip gap\n sat at 8px while each chip's own horizontal padding was\n `px-2.5` (10px) — the chip's internal space was wider\n than the gap between chips, so adjacent chips read as\n \"touching\" rather than \"neighboring\". Bumping the gap\n to 10px makes inter-chip = chip-padding, visually\n balancing the rhythm. Sibling treatment to R298 title-\n block `gap-2.5` (brand-logo ↔ kicker/title) and R326\n chrome strip `gap-2` extension family. Layout strip:\n R298 title-block gap-2.5 (top of canvas)\n R328 chip-row gap-2.5 (below title) ← NEW\n R326 chrome gap-2 (bottom of canvas)\n Risk-bounded: chip-row uses `flex-wrap`; if it wraps to\n a new line on narrow viewports the row-gap also bumps to\n 10px, which only helps mobile rhythm. Topo-overlap-test\n is HTML-overlay-only at this scope; SVG viewBox layout\n untouched. */}\n <div className=\"flex flex-wrap items-center gap-2.5 text-xs\">\n {/* Issue #87: ring | grid layout toggle — segmented control,\n persisted to localStorage anet-topo-layout.\n Round 163 / Loop: bring the layout toggle into the R154\n chrome-button focus convention. Pre-R163 the buttons had\n aria-pressed + transition-colors but no focus-visible ring\n (browser default — invisible against dark canvas) and the\n inactive variant only nudged text from gray-500 → gray-400\n on hover with no bg tint, so the hover-to-click affordance\n was barely perceptible.\n\n R163 closes both gaps to match R154:\n focus-visible:ring-2 focus-visible:ring-cyan-400/60\n → keyboard users see exactly which segment is focused\n hover:bg-cyan-500/5 (inactive)\n → mouse hover shows a faint cyan ghost of the active\n state, signalling 'click to switch'\n hover:bg-cyan-500/20 (active)\n → active button responds too, says 'still clickable'\n data-topo-chrome-layout for testability symmetric with the\n R154 chrome buttons (data-topo-chrome-zoom-in / -reset /\n -fullscreen etc).\n\n Round 260 / Loop: chip-row semantic gap — Layout toggle is\n the only CONTROL in the chip row; everything that follows\n (working / online / pressure / vendor letters / active-\n links / filter pills / freshness) is READ-ONLY display.\n Pre-R260 all 8 children sat at uniform gap-2 (8px) — the\n spatial signal read as \"8 separate things\" instead of\n \"1 control + 7 display\". mr-1 (4px) on the Layout toggle\n stacks on top of the parent flex's gap-2 (8px) for an\n effective 12px gap before the first status chip — same\n law-of-proximity pattern R255 applied to the bottom-right\n chrome strip (fleet vs view groups). data-topo-chrome-\n layout-trailer marks the boundary surface for the gap\n probe. */}\n {/* Round 268 / Loop: Layout toggle border unified with the\n chrome strip's theme-aware borderColor. Pre-R268 the\n wrapper + Grid button's internal divider used hardcoded\n `border-gray-500/25` (pale gray, fixed in both themes)\n while the bottom-right chrome strip (nodeSize, zoom)\n used pal.containerBorder (cyber #2a2a4a dark indigo ↔\n light #e3e6eb pale gray). Visible mismatch in cyber\n theme: Layout toggle border read as pale gray while\n chrome strip borders read as darker indigo — two\n different border colors on visually-analogous\n segmented controls. R268 replaces the hardcoded class\n with inline pal.containerBorder + a border-color\n transition, so the Layout toggle (a) matches the chrome\n strip border color and (b) joins the canvas-wide\n theme-ease vocabulary (eases on cyber↔light toggle\n instead of snapping). Same change applied to the Grid\n button's border-l on line ~1493. */}\n {/* Round 329 / Loop: Layout toggle wrapper `mr-1` → `mr-0.5`\n to compensate for R328's chip-row gap bump (8 → 10 px).\n R260 designed for an effective 12 px gap between the\n Layout CONTROL and the first DISPLAY chip (working /\n online / etc): mr-1 (4 px) + chip-row gap-2 (8 px) = 12.\n R328 widened chip-row to gap-2.5 (10 px), pushing the\n effective gap to 14 px — semantically still \"control\n vs display\" but louder than R260 specified.\n R329 dials mr-1 → mr-0.5 (2 px) so the effective gap\n returns to 12 px (mr-0.5 + chip-row gap-2.5 = 2 + 10).\n Keeps the law-of-proximity semantic R260 designed\n while honoring R328's wider baseline rhythm. data-topo-\n chrome-layout-trailer attr unchanged — it still marks\n the boundary surface for the gap probe. */}\n <div\n className=\"mr-0.5 inline-flex rounded-md border overflow-hidden\"\n style={{ borderColor: pal.containerBorder, transition: 'border-color 200ms ease-out' }}\n role=\"group\"\n aria-label=\"Topology layout\"\n data-topo-chrome-layout-trailer\n >\n <button\n onClick={() => { popChrome('layout-ring'); if (layout !== 'ring') toggleLayout(); }}\n aria-pressed={layout === 'ring'}\n title=\"Ring layout (l to toggle)\"\n data-topo-chrome-layout=\"ring\"\n data-topo-chrome-layout-active={layout === 'ring' ? 'true' : 'false'}\n data-topo-chrome-layout-ring-popping={chromePopping === 'layout-ring' ? 'true' : 'false'}\n // Round 196 / Loop: add active: (pressed) state for tactile\n // click feedback — bridges mouse-down → R186/R184/R192 pop-on-\n // release. Selected variant deepens to cyan-500/25 (one tier\n // above its hover:cyan-500/20); unselected variant deepens\n // to cyan-500/15 (one tier above its hover:cyan-500/5).\n // Round 249 / Loop: chrome-pop joins the click handshake —\n // mouse-down deepens cyan press (R196), release fires\n // .anet-chrome-pop on the button (R249) AND triggers\n // toggleLayout if state changes. The pop runs even when\n // clicking the already-active layout (no state change),\n // confirming the click was received either way.\n /* Round 306 / Loop: focus-visible:ring-2 → ring-1 unifies\n with the rest of the chrome button family. Pre-R306\n the Layout toggle (Ring/Grid) used `focus-visible:\n ring-2` (2px outline) while nodeSize S/M/L (line\n ~7291), zoom -/+ (~7328/~7395), reset (~7417), and\n fullscreen (~7477) all use `focus-visible:ring-1`\n (1px outline). Two different focus-ring widths on\n visually-analogous chrome controls — same R268\n border-color unification + R288 icon-stroke\n unification family. Reducing Ring/Grid to ring-1\n lets all 7 chrome buttons share one focus-ring\n weight; cyan-400/60 + ring-inset retained. The\n R163/R196 hover/active deeps + R249 chrome-pop\n click feedback continue unchanged. */\n className={`px-2.5 py-1 transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset ${layout === 'ring' ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'text-gray-400 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15'} ${chromePopping === 'layout-ring' ? ' anet-chrome-pop' : ''}`}\n >\n Ring\n </button>\n <button\n onClick={() => { popChrome('layout-grid'); if (layout !== 'grid') toggleLayout(); }}\n aria-pressed={layout === 'grid'}\n title=\"Grid layout (l to toggle)\"\n data-topo-chrome-layout=\"grid\"\n data-topo-chrome-layout-active={layout === 'grid' ? 'true' : 'false'}\n data-topo-chrome-layout-grid-popping={chromePopping === 'layout-grid' ? 'true' : 'false'}\n // Round 196 / Loop: R163 layout-toggle Grid variant picks up\n // press-state — same tier pattern as Ring above.\n // Round 249 / Loop: chrome-pop on click — same as Ring.\n // Round 306 / Loop: focus-visible:ring-2 → ring-1 sibling\n // change to Ring above — unifies focus-ring width across\n // all chrome buttons.\n className={`px-2.5 py-1 border-l transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset ${layout === 'grid' ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'text-gray-400 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15'} ${chromePopping === 'layout-grid' ? ' anet-chrome-pop' : ''}`}\n /* Round 268 / Loop: Grid button's left border (the\n internal divider between Ring and Grid) picks up\n pal.containerBorder, matching the wrapper change at\n line ~1460 and the chrome strip's segmented borders\n (nodeSize, zoom). transition-colors className covers\n the border-color eased on theme toggle. */\n style={{ borderColor: pal.containerBorder }}\n >\n Grid\n </button>\n </div>\n {/* R79: working + online count chips become hover affordances —\n extends R77's chip-hover pattern to status counts. Hover the\n working chip → setHoveredStatus('working') so the R55 dim\n mechanic kicks in (same as hovering the SVG legend \"working\"\n row, just a different surface for the same gesture).\n The online chip uses the same setHoveredStatus('idle') path\n when there's idle but no working — falling back to working\n if any working node exists; \"online\" without a single bucket\n isn't part of the R55 type set, so this chip routes to the\n dominant online sub-tier instead of inventing a new state.\n Cursor only flips when there's anything to highlight. */}\n {/* R82: pin-mirror. R60 pressure-bar segments visualise the\n pinned status via an inset boxShadow; R61 legend rows via a\n concentric r=8 ring; the chip-row chips next to them did\n not — so a user who pinned via Cmd+K (R69) or the legend\n had no chip-row signal that \"working\" was the current\n filter. Mirror the pressure-bar treatment here so all\n status surfaces sing in unison. The online chip mirrors\n for idle pins (working ⊆ online; the routing in\n onMouseEnter already treats online as the idle fallback). */}\n {(() => {\n // R113 / Loop: extend the R97-R102/R101 alias-list tooltip\n // sweep to the remaining chip-row affordances. R79 made\n // these chips hoverable; their generic \"Hover to highlight\"\n // titles never said WHICH nodes match. Compute the alias\n // lists once for both chips to share.\n const workingAliases = onlineNodes.filter(s => s.status === 'working').map(s => s.alias);\n const onlineAliases = onlineNodes.map(s => s.alias);\n const truncate = (list: string[]) => {\n const head = list.slice(0, 8).join(', ');\n const tail = list.length > 8 ? ` + ${list.length - 8} more` : '';\n return head + tail;\n };\n const workingTitle = workingCount === 0\n ? undefined\n : pinnedStatus === 'working'\n ? `${truncate(workingAliases)} — pinned, Esc to clear`\n : `${truncate(workingAliases)} — hover highlights, click to pin`;\n // R140: online chip title gains a \"click to open /nodes\" tail\n // when interactive. R79 made the cursor pointer-shaped but\n // wired nothing; R136 + R139 closed the same lie on two\n // sibling chips by wiring real actions. The online chip\n // can't pin a single status (online = working + idle,\n // not a single pinnedStatus value), so a pin idiom would\n // be semantically wrong. /nodes is the natural full-list\n // destination — same \"click chip for the full list\" idiom\n // the active-links chip uses (R136 → /messages).\n const onlineTitle = onlineNodes.length === 0\n ? undefined\n : pinnedStatus === 'idle'\n ? `${truncate(onlineAliases)} — pinned, Esc to clear`\n : `${truncate(onlineAliases)} — hover highlights · click to open /nodes`;\n return (\n <>\n <span\n // Round 201 / Loop: the working chip joins the\n // \"chip's hover state deepens its OWN identity colour\"\n // family that R193 opened (active-links chip) and R195\n // extended (recent-panel footer). Pre-R201 hovering the\n // working chip fired R55 canvas dim + chip-row highlight\n // but the chip itself stayed at bg-green-500/10 — cause\n // silent, effect loud. R201 deepens its OWN green hue\n // (10→15 bg, 20→30 border) only when clickable.\n // transition-colors duration-200 blends the swap to\n // match R193's timing on the active-links chip.\n /* Round 232 / Loop: HTML chip row picks up the\n tabular-nums info-density treatment R224-R230\n established on the SVG side. The chip text reads\n \"{N} working\"; when workingCount crosses 9→10\n the leading digit's width shift propagates the\n trailing ' working' label right by the digit-vs-\n control glyph delta, and the chip's right edge\n re-flows because the parent is inline flex. The\n Tailwind `tabular-nums` utility sets font-variant-\n numeric: tabular-nums, locking the digit width\n so the chip text + chip width stay stable across\n all counter values. Sibling chips (online,\n active-links) get the same treatment in this\n round so the three-chip row reads uniformly.\n 7th surface in the info-density tabular-nums\n sweep — and the first on the HTML side\n (previous 6 were SVG <text>/<tspan>). */\n className={`tabular-nums font-medium px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-colors duration-200 ${\n workingCount > 0\n ? 'bg-green-500/10 text-green-300 border-green-500/20 hover:bg-green-500/15 hover:border-green-500/30'\n : 'bg-green-500/10 text-green-300 border-green-500/20'\n }`}\n data-working-chip\n data-working-chip-aliases={workingAliases.join(',')}\n data-pin-mirror={pinnedStatus === 'working' ? 'true' : 'false'}\n data-working-chip-clickable={workingCount > 0 ? 'true' : 'false'}\n data-working-chip-empty={workingCount === 0 ? 'true' : 'false'}\n title={workingTitle}\n role={workingCount > 0 ? 'button' : undefined}\n tabIndex={workingCount > 0 ? 0 : undefined}\n aria-pressed={workingCount > 0 ? (pinnedStatus === 'working') : undefined}\n // Round 180 / Loop: pin-mirror inset rings now ease in/out\n // instead of snapping. R165 added this transition to the\n // pressure-bar segments; R180 closes the smooth-pin-mirror\n // family across the three remaining chip-row pin chips\n // (working / online / vendor letter). The visual is small\n // — a 1-2 px inset double ring — but the eye catches the\n // pop on every pin/unpin without the ease.\n // Round 201 / Loop: inline transition list now also covers\n // background-color + border-color so the R201 hover tint\n // eases. Tailwind transition-colors on the className would\n // be overridden by this inline declaration, so we splice\n // the colour properties directly into the existing\n // R180 box-shadow transition.\n // Round 205 / Loop: chip recedes to opacity 0.5 when its\n // tier is empty (workingCount=0). Pre-R205 \"0 working\"\n // displayed at full bg-green-500/10 chrome — visually\n // indistinguishable from \"12 working\". Eye got zero\n // empty-tier signal. R205 mirrors R204's legend count\n // recede-on-empty pattern at the chip-row scope. Inline\n // transition list extends `opacity 200ms ease-out` so\n // the crossing-zero ease matches R201's bg/border\n // timing. Empty-state combines with R139's clickable=\n // false + tooltip-undefined: visual + interactive +\n // affordance all say \"this tier has nothing to act on\".\n style={{\n cursor: workingCount > 0 ? 'pointer' : undefined,\n opacity: workingCount === 0 ? 0.5 : 1,\n boxShadow: pinnedStatus === 'working' ? 'inset 0 0 0 1px #4ade80, inset 0 0 0 2px rgba(255,255,255,0.45)' : undefined,\n transition: 'box-shadow 150ms ease-out, background-color 200ms ease-out, border-color 200ms ease-out, opacity 200ms ease-out',\n }}\n onMouseEnter={() => { if (workingCount > 0) setHoveredStatus('working'); }}\n onMouseLeave={() => setHoveredStatus(prev => prev === 'working' ? null : prev)}\n // R139: the title hover-text has been promising \"click to\n // pin\" since R79 but no onClick was ever wired. The cursor:\n // pointer at line 1363 set up the same lie R136 fixed on\n // the active-links chip. Wire it now: click toggles the\n // status pin to 'working', composing with R60 (pressure-\n // bar segments) and R61 (legend rows) — three different\n // surfaces that all toggle the same pinnedStatus. boxShadow\n // pin-mirror at line 1364 already reflects the state; aria-\n // pressed now exposes it for screen readers too.\n onClick={() => {\n if (workingCount > 0) setPinnedStatus(prev => prev === 'working' ? null : 'working');\n }}\n onKeyDown={(e) => {\n if (workingCount === 0) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setPinnedStatus(prev => prev === 'working' ? null : 'working');\n }\n }}\n >\n {/* Round 337 / Loop: split working chip into digit +\n \" working\" unit, with the unit at opacity-70.\n Extends the R333/R335/R336 chip-internal-hierarchy\n arc from SVG (panel headers) and pin-chip prefix\n surfaces into HTML chip-row chips. Recurring\n pattern: small label spans demote, value stays\n prominent. data-working-chip-unit exposes the\n span for tests. */}\n {workingCount}<span className=\"opacity-70\" data-working-chip-unit> working</span>\n </span>\n <span\n // Round 201 / Loop: online chip — mirror of the working\n // chip treatment above. cyan hue 10→15 bg + 20→30 border\n // on hover, only when there's at least one online node\n // to highlight. Three sibling chips in the chip row now\n // all speak the same gesture vocabulary:\n // working chip · green 10→15 (R201)\n // online chip · cyan 10→15 (R201)\n // active-links · gray → cyan (R193)\n /* Round 232 / Loop: tabular-nums on online chip\n (sibling treatment to working chip — same row,\n same digit-jitter physics on count crossings). */\n className={`tabular-nums font-medium px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-colors duration-200 ${\n onlineNodes.length > 0\n ? 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20 hover:bg-cyan-500/15 hover:border-cyan-500/30'\n : 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20'\n }`}\n data-online-chip\n data-online-chip-aliases={onlineAliases.join(',')}\n data-pin-mirror={pinnedStatus === 'idle' ? 'true' : 'false'}\n data-online-chip-clickable={onlineNodes.length > 0 ? 'true' : 'false'}\n data-online-chip-empty={onlineNodes.length === 0 ? 'true' : 'false'}\n title={onlineTitle}\n role={onlineNodes.length > 0 ? 'link' : undefined}\n tabIndex={onlineNodes.length > 0 ? 0 : undefined}\n // R180: smooth-pin-mirror family — see working chip above.\n // R201: inline transition list also covers bg + border so\n // the new R201 hover tint eases (mirror of working chip).\n // R205: empty-tier recede — opacity 0.5 when onlineNodes\n // is empty (mirror of working chip above).\n style={{\n cursor: onlineNodes.length > 0 ? 'pointer' : undefined,\n opacity: onlineNodes.length === 0 ? 0.5 : 1,\n boxShadow: pinnedStatus === 'idle' ? 'inset 0 0 0 1px #67e8f9, inset 0 0 0 2px rgba(255,255,255,0.45)' : undefined,\n transition: 'box-shadow 150ms ease-out, background-color 200ms ease-out, border-color 200ms ease-out, opacity 200ms ease-out',\n }}\n onMouseEnter={() => {\n // If a working filter would isolate nothing, route to idle.\n const idleCount = onlineNodes.length - workingCount;\n if (workingCount > 0) setHoveredStatus('working');\n else if (idleCount > 0) setHoveredStatus('idle');\n }}\n onMouseLeave={() => setHoveredStatus(prev => prev === 'working' || prev === 'idle' ? null : prev)}\n // R140: click → /nodes. Mirrors R136 active-links→/messages\n // idiom. The chip's hover semantics (R79 highlight all\n // online) keep their meaning — hover for canvas preview,\n // click for the full list. Pinning idle here would\n // semantically misrepresent the chip (which means\n // working+idle), so we navigate instead of pin.\n onClick={() => {\n if (onlineNodes.length > 0) router.push('/nodes');\n }}\n onKeyDown={(e) => {\n if (onlineNodes.length === 0) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n router.push('/nodes');\n }\n }}\n >\n {/* R337 sibling — online chip unit demotion. */}\n {onlineNodes.length}<span className=\"opacity-70\" data-online-chip-unit> online</span>\n </span>\n </>\n );\n })()}\n {/* Round 31 / Loop: fleet-health pressure bar. The \"X working /\n Y online\" chips already carry the raw counts; the bar lets\n the eye get the working/idle/offline RATIO in one glance\n without mental math. Stacked 3-segment chip, ~64 px wide. */}\n {(() => {\n const w = workingCount;\n const i = onlineNodes.length - workingCount; // idle = online - working\n const o = offlineNodes.length;\n const total = w + i + o;\n if (total === 0) return null;\n // Round 60 / Loop: each segment toggles a sticky filter via\n // `pinnedStatus`. Click the working segment → all non-working\n // nodes dim; click again → release. Segments share width with\n // their proportion of `total`, so a 1-node working share is\n // ~3 px wide on a 64-px bar. We pad the click target with a\n // negative-margin overlay wrapper to give thin slices a\n // 14-px minimum hit area without disturbing the rendered\n // chip width. cursor:pointer + a one-line title hint at the\n // affordance.\n const seg = (n: number, color: string, key: 'working' | 'idle' | 'offline', label: string) => {\n if (n === 0) return null;\n const isPinned = pinnedStatus === key;\n // R102: list the aliases that match this segment's bucket\n // so the title answers WHICH n, not just HOW MANY. Closes\n // the last \"info-density gap\" in the chip-row surfaces\n // (R97 pills / R99 group labels / R101 vendor letters all\n // already enumerate). Truncates at 8 with \"+N more\".\n const matchAliases = key === 'working'\n ? onlineNodes.filter(s => s.status === 'working').map(s => s.alias)\n : key === 'idle'\n ? onlineNodes.filter(s => s.status !== 'working').map(s => s.alias)\n : offlineNodes.map(s => s.alias);\n const previewList = matchAliases.slice(0, 8).join(', ');\n const suffix = matchAliases.length > 8 ? ` + ${matchAliases.length - 8} more` : '';\n const titleAction = isPinned ? 'click to release filter' : 'click to highlight';\n return (\n <span\n key={key}\n data-pressure-seg={key}\n data-pressure-seg-aliases={matchAliases.join(',')}\n data-pressure-seg-hovered={hoveredStatus === key ? 'true' : 'false'}\n role=\"button\"\n tabIndex={0}\n aria-pressed={isPinned}\n className=\"anet-topo-chip-focus\"\n title={`${n} ${label}\\n${previewList}${suffix}\\n${titleAction}`}\n // Round 165 / Loop: smooth width transitions on the\n // pressure-bar segments. Pre-R165 the widths snapped\n // instantly when fleet composition shifted (a node\n // going idle → working would visibly jump the green\n // segment by a few px). 220ms ease-out makes the bar\n // visually breathe with state — segment shifts now\n // glide into place instead of cutting. Pure CSS\n // transition on width; respects prefers-reduced-\n // motion via globals.css blanket override that\n // neutralises transition-duration universally.\n // boxShadow gets its own transition so pin\n // state-changes also fade smoothly, not snap.\n //\n // Round 210 / Loop: segment deepens its OWN colour on\n // hover via filter: brightness(1.2). Closes the\n // chip-row \"hover deepens own identity\" family at the\n // pressure-bar scope — R83 already setHoveredStatus\n // to drive canvas dim, but the segment itself stayed\n // at flat `background: color`. R210 makes the cause\n // element (segment) light up with the same gesture\n // it fires on the canvas (effect element). Family\n // surfaces (6 with R210):\n // R193 active-links chip · gray → cyan\n // R195 recent-panel footer · gray → cyan\n // R201 working / online · own /10 → /15 (×2)\n // R202 vendor letter · per-vendor color-mix\n // R210 pressure-bar seg · brightness(1.2) ← NEW\n // brightness(1.2) lightens the segment's own hue by\n // 20% — keeps the tier identity (green stays green,\n // teal stays teal, gray stays gray) while signalling\n // \"this is hovered\". transition adds filter 150ms\n // ease-out alongside the existing width / box-shadow.\n style={{\n width: `${(n / total) * 100}%`,\n background: color,\n height: '100%',\n cursor: 'pointer',\n boxShadow: isPinned ? `inset 0 0 0 1px ${color}, inset 0 0 0 2px rgba(255,255,255,0.6)` : undefined,\n filter: hoveredStatus === key ? 'brightness(1.2)' : undefined,\n transition: 'width 220ms ease-out, box-shadow 150ms ease-out, filter 150ms ease-out',\n }}\n onClick={(e) => {\n e.stopPropagation();\n setPinnedStatus(prev => prev === key ? null : key);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setPinnedStatus(prev => prev === key ? null : key);\n }\n }}\n // R83: hover preview — segments now match the R55 legend,\n // R79 working/online chips, and R80 vendor letters in\n // offering a hover-transient highlight before the click\n // commits to a pin. Users get to feel what the filter\n // does before they lock it in. Same activeStatus =\n // hoveredStatus ?? pinnedStatus formula; releasing the\n // pointer falls back to the pin if one is set, or to\n // baseline. Thin (3-px) segments still hit-test fine\n // because the chip itself is a flex row — span doesn't\n // need any extra hit padding for hover.\n onMouseEnter={() => setHoveredStatus(key)}\n onMouseLeave={() => setHoveredStatus(prev => prev === key ? null : prev)}\n />\n );\n };\n // Round 47 / Loop: hidden on mobile — at <640px the chip row\n // wraps to multiple lines and crowds the topology header;\n // pressure ratio is best read with the working+online raw\n // counts (kept visible) anyway.\n return (\n <span\n className=\"hidden sm:inline-flex items-center gap-2 px-2.5 py-1 rounded-md bg-gray-500/10 text-gray-400 border border-gray-500/20 font-mono\"\n title={`${w} working · ${i} idle · ${o} offline`}\n data-fleet-pressure\n >\n <span className=\"text-[10px] tracking-wide\">pressure</span>\n <span className=\"inline-flex h-1.5 w-16 rounded-full overflow-hidden\" style={{ background: 'rgb(75 85 99 / 0.25)' }}>\n {seg(w, isLight ? '#059669' : '#22c55e', 'working', 'working')}\n {seg(i, isLight ? '#0d9488' : '#2dd4bf', 'idle', 'idle')}\n {seg(o, isLight ? '#94a3b8' : '#6b7280', 'offline', 'offline')}\n </span>\n </span>\n );\n })()}\n {/* Round 64 / Loop: active-filter pills. When pinnedStatus or\n pinnedGroup is set, show a small \"filter: <key> ×\" pill so\n the user can see the pin from the chip row even if they\n scrolled the canvas off-screen. × button clears the\n specific pin (Esc still clears all — both paths are\n valid). pinnedStatus and pinnedGroup pin independently so\n both pills may render simultaneously. */}\n {/* R67: pills enter via anet-fade-in so they appear softly\n instead of popping. The \"filter: \" prefix collapses below\n sm — at narrow viewports the chip row is precious real\n estate (R47 already hides pressure / vendor / freshness),\n so dropping the redundant label keeps the working/idle/\n alpha keys readable without overflow. */}\n {/* R71: each pill picks up a \"· N\" match count tail. Tells the\n user at a glance how many sessions the active filter\n matches without scanning the canvas. Counts come from the\n already-computed workingCount + onlineNodes + offlineNodes\n for status, and groupKeys for group. */}\n {/* R73: entire pill body is a click-to-clear target — matches\n the Notion / Linear tag UX (the whole chip releases). The\n × keeps its dedicated <button> + aria-label for screen\n readers; the outer span just adds an extra mouse-friendly\n hit area with a title hint. ×'s onClick stopPropagation\n so the redundant outer onClick doesn't double-fire (no\n functional difference since both clear, but cleaner\n event flow). */}\n {pinnedStatus && (() => {\n const matchCount = pinnedStatus === 'working' ? workingCount\n : pinnedStatus === 'idle' ? (onlineNodes.length - workingCount)\n : offlineNodes.length;\n // Round 97 / Loop: the pill says HOW MANY but not WHICH.\n // Hovering it now shows the matched alias list in the\n // tooltip — answers \"exactly who is this filtering to\"\n // without forcing the user to scan the dim mask on the\n // canvas. Truncates at 8 names so a 50-node working\n // bucket doesn't produce a 12-line tooltip; the count\n // chip already answers \"how many overall\".\n const matchAliases = pinnedStatus === 'working'\n ? onlineNodes.filter(s => s.status === 'working').map(s => s.alias)\n : pinnedStatus === 'idle'\n ? onlineNodes.filter(s => s.status !== 'working').map(s => s.alias)\n : offlineNodes.map(s => s.alias);\n const matchPreview = matchAliases.slice(0, 8).join(', ');\n const matchSuffix = matchAliases.length > 8 ? ` + ${matchAliases.length - 8} more` : '';\n return (\n <span\n data-active-filter=\"status\"\n data-filter-match-count={matchCount}\n data-filter-match-aliases={matchAliases.join(',')}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus\"\n title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}\n onClick={() => setPinnedStatus(null)}\n style={{\n background: pinnedStatus === 'working' ? (isLight ? '#05966914' : '#22c55e1f')\n : pinnedStatus === 'idle' ? (isLight ? '#0d948814' : '#2dd4bf1f')\n : (isLight ? '#94a3b814' : '#6b72801f'),\n color: pinnedStatus === 'working' ? (isLight ? '#047857' : '#86efac')\n : pinnedStatus === 'idle' ? (isLight ? '#0f766e' : '#5eead4')\n : (isLight ? '#475569' : '#9ca3af'),\n borderColor: 'currentColor',\n cursor: 'pointer',\n }}\n >\n <span><span className=\"hidden sm:inline opacity-70\" data-filter-prefix>filter: </span>{pinnedStatus}<span className=\"opacity-70 tabular-nums\" data-filter-pill-count> · {matchCount}</span></span>\n <button\n type=\"button\"\n aria-label={`Clear ${pinnedStatus} filter`}\n onClick={(e) => { e.stopPropagation(); setPinnedStatus(null); }}\n className=\"ml-0.5 leading-none hover:opacity-70\"\n style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}\n >×</button>\n </span>\n );\n })()}\n {pinnedGroup && (() => {\n const matchCount = Object.values(groupKeys).filter(k => k === pinnedGroup).length;\n // R97: list group members in the tooltip.\n const matchAliases = Object.entries(groupKeys)\n .filter(([, key]) => key === pinnedGroup)\n .map(([alias]) => alias);\n const matchPreview = matchAliases.slice(0, 8).join(', ');\n const matchSuffix = matchAliases.length > 8 ? ` + ${matchAliases.length - 8} more` : '';\n return (\n <span\n data-active-filter=\"group\"\n data-filter-match-count={matchCount}\n data-filter-match-aliases={matchAliases.join(',')}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus\"\n title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}\n onClick={() => setPinnedGroup(null)}\n style={{\n background: isLight ? '#67e8f914' : '#67e8f91f',\n color: pal.legendAccent,\n borderColor: 'currentColor',\n cursor: 'pointer',\n }}\n >\n <span><span className=\"hidden sm:inline opacity-70\" data-filter-prefix>filter: </span>{pinnedGroup}<span className=\"opacity-70 tabular-nums\" data-filter-pill-count> · {matchCount}</span></span>\n <button\n type=\"button\"\n aria-label={`Clear group filter ${pinnedGroup}`}\n onClick={(e) => { e.stopPropagation(); setPinnedGroup(null); }}\n className=\"ml-0.5 leading-none hover:opacity-70\"\n style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}\n >×</button>\n </span>\n );\n })()}\n {/* R89: vendor pin gets its own filter pill, matching the R64\n status + group pattern. R88 added the pin but only the\n letter itself carried the state; the chip-row had no\n \"filter: A · 2 ×\" affordance the other two pins have. The\n pill colour borrows the vendor's own swatch so each pin\n still reads in its native hue (A green, O cyan, 书 blue,\n ? slate). Same body-click-clears + × button pattern as\n R64. */}\n {pinnedVendor && (() => {\n const matchEntry = vendorDist.find(v => v.initial === pinnedVendor);\n const matchCount = matchEntry?.count ?? 0;\n const vendorColor = matchEntry?.color ?? pal.legendText;\n // R97: list vendor users in the tooltip. The vendorIdentity\n // resolver maps model strings to a vendor initial — match\n // any session whose initial equals the pinned letter (with\n // unknowns folded to '?').\n const matchAliases = [...onlineNodes, ...offlineNodes]\n .filter(s => {\n const v = vendorForModel(s.model);\n return (v.id === 'unknown' ? '?' : v.initial) === pinnedVendor;\n })\n .map(s => s.alias);\n const matchPreview = matchAliases.slice(0, 8).join(', ');\n const matchSuffix = matchAliases.length > 8 ? ` + ${matchAliases.length - 8} more` : '';\n return (\n <span\n data-active-filter=\"vendor\"\n data-filter-match-count={matchCount}\n data-filter-match-aliases={matchAliases.join(',')}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus\"\n title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear vendor filter'}\n onClick={() => setPinnedVendor(null)}\n style={{\n background: `${vendorColor}1f`,\n color: vendorColor,\n borderColor: 'currentColor',\n cursor: 'pointer',\n }}\n >\n <span><span className=\"hidden sm:inline opacity-70\" data-filter-prefix>filter: </span>{pinnedVendor}<span className=\"opacity-70 tabular-nums\" data-filter-pill-count> · {matchCount}</span></span>\n <button\n type=\"button\"\n aria-label={`Clear vendor filter ${pinnedVendor}`}\n onClick={(e) => { e.stopPropagation(); setPinnedVendor(null); }}\n className=\"ml-0.5 leading-none hover:opacity-70\"\n style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}\n >×</button>\n </span>\n );\n })()}\n {/* R119: edge pin filter pill — completes the R64 / R89 pill\n pattern across all four pin dimensions. R116 added\n pinnedEdgeKey but the chip row never grew the matching\n pill, leaving the locked flow visible only on the canvas +\n recent-signal row tint. This pill shows \"filter:\n alpha→beta · 3\" so the locked flow appears in the same\n row as the other three pin types. Body-click + × button\n clear, same pattern as R64. */}\n {pinnedEdgeKey && (() => {\n const link = flowLinks.find(l => l.key === pinnedEdgeKey);\n if (!link) return null;\n // R150 / Loop: extend the hot-lane amber convention from the\n // canvas badge (R126) / recent-row count (R127) / panel\n // header (R129) to the R119 edge filter pill — when the\n // pinned edge has count ≥ 10 the count tspan flips to\n // amber + 700 weight, and the tooltip grows a \"(hot lane\n // · ≥ 10)\" marker. Closes the 4th hot-lane surface,\n // completing R150's milestone: every place that surfaces\n // an edge count now uses the same amber-when-hot vocab.\n const isHot = link.count >= 10;\n const hotStroke = isLight ? '#d97706' : '#fbbf24';\n return (\n <span\n data-active-filter=\"edge\"\n data-filter-match-count={link.count}\n data-filter-match-aliases={`${link.from},${link.to}`}\n data-active-filter-edge-hot={isHot ? 'true' : 'false'}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus\"\n title={`${link.from} → ${link.to} (${link.count} msg${link.count === 1 ? '' : 's'}${isHot ? ', hot lane · ≥ 10' : ''}) — click to clear`}\n onClick={() => setPinnedEdgeKey(null)}\n style={{\n background: isLight ? `${pal.flowEdge}14` : `${pal.flowEdge}1f`,\n color: pal.flowEdge,\n borderColor: 'currentColor',\n cursor: 'pointer',\n }}\n >\n <span>\n <span className=\"hidden sm:inline opacity-70\" data-filter-prefix>filter: </span>\n {link.from}→{link.to}\n {/* Round 323 / Loop: edge filter pill count digit picks\n up tabular-nums (Tailwind class on both cold +\n hot branches). Sibling treatment to the status /\n group / vendor pin pills (R323 replace_all upstream\n in this same round added `tabular-nums` to those\n three pills' count spans). Pre-R323 a matchCount /\n link.count crossing 9→10 widened the digit and\n shifted the trailing × button right ~3px in font-\n mono (mono digits still have natural-vs-tabular\n variance). Locks the slot so the × button stays\n planted as the count grows. 9th surface in the\n info-density tabular-nums sweep after R322 panel\n hot count. */}\n {isHot ? (\n <span\n className=\"opacity-90 tabular-nums\"\n style={{ color: hotStroke, fontWeight: 700 }}\n data-active-filter-edge-count-hot\n >\n {' · '}{link.count}\n </span>\n ) : (\n <span className=\"opacity-70 tabular-nums\" data-active-filter-edge-count>\n {' · '}{link.count}\n </span>\n )}\n </span>\n <button\n type=\"button\"\n aria-label={`Clear edge filter ${link.from} → ${link.to}`}\n onClick={(e) => { e.stopPropagation(); setPinnedEdgeKey(null); }}\n className=\"ml-0.5 leading-none hover:opacity-70\"\n style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}\n >×</button>\n </span>\n );\n })()}\n {/* Round 124 / Loop: pin-intersection summary chip. The four\n filter pills (R64 status, R63 group, R89 vendor, R119\n edge) each report their own dim's match count in\n isolation — that answers \"what does THIS filter catch\"\n but not \"what survives ALL pins\". Since the node-opacity\n chain AND-composes the four pin dimensions, two active\n pins routinely produce an intersection smaller than\n either individual count. With three or four pins active\n the gap widens further. This chip appears ONLY when\n ≥ 2 pin dims are active (a single pin's pill already\n tells the whole story) and shows the count of nodes\n that satisfy every active pin simultaneously. Color is\n deliberately neutral (gray) since the chip represents\n the AND of mixed-color filters — borrowing any one of\n the pill hues would mis-signal which dim dominates.\n Tooltip lists the surviving aliases with the same\n 8-truncate + \"+N more\" pattern as the individual pill\n tooltips (R97). No click handler — it's a pure readout;\n Esc / per-pill × are still the cancel paths. */}\n {(() => {\n const pinDimCount =\n (pinnedStatus ? 1 : 0) +\n (pinnedGroup ? 1 : 0) +\n (pinnedVendor ? 1 : 0) +\n (pinnedEdgeKey ? 1 : 0);\n if (pinDimCount < 2) return null;\n const edgeLink = pinnedEdgeKey\n ? flowLinks.find(l => l.key === pinnedEdgeKey)\n : null;\n const edgeEndpoints: Set<string> | null = edgeLink\n ? new Set([edgeLink.from, edgeLink.to])\n : null;\n const allSessions = [...onlineNodes, ...offlineNodes];\n const survivors = allSessions.filter(s => {\n const isOnline = s.status !== 'offline';\n if (pinnedStatus === 'working' && s.status !== 'working') return false;\n if (pinnedStatus === 'idle' && !(isOnline && s.status !== 'working')) return false;\n if (pinnedStatus === 'offline' && isOnline) return false;\n if (pinnedGroup) {\n const gk = groupKeys[s.alias] ?? s.alias;\n if (gk !== pinnedGroup) return false;\n }\n if (pinnedVendor) {\n const v = vendorForModel(s.model);\n const initial = v.id === 'unknown' ? '?' : v.initial;\n if (initial !== pinnedVendor) return false;\n }\n if (edgeEndpoints && !edgeEndpoints.has(s.alias)) return false;\n return true;\n });\n const matchAliases = survivors.map(s => s.alias);\n const matchPreview = matchAliases.slice(0, 8).join(', ');\n const matchSuffix = matchAliases.length > 8 ? ` + ${matchAliases.length - 8} more` : '';\n const isEmpty = matchAliases.length === 0;\n const tooltip = !isEmpty\n ? `${matchPreview}${matchSuffix} — nodes passing all ${pinDimCount} pinned filters`\n : `No nodes pass all ${pinDimCount} pinned filters — release one to widen (Esc clears all)`;\n // R125: when the intersection drops to zero, the chip flips\n // from neutral gray to a warning amber. Zero-overlap is the\n // exact case users get confused — canvas dims to 0.28\n // everywhere, no positive signal explains why. The \"· 0\"\n // tail in neutral gray reads as just another number,\n // indistinguishable from \"· 12\". Amber + a ⚠ glyph lifts\n // it to \"your filters cancel out\" at a glance. Color\n // choice: amber (#d97706 light, #fbbf24 dark) — same hue\n // family as warning chips elsewhere in the dashboard,\n // distinct from any pill color (status green/teal/slate,\n // group cyan, vendor varies, edge cyan, neutral gray for\n // non-empty intersection) so the empty state stands out.\n const emptyColor = isLight ? '#d97706' : '#fbbf24';\n return (\n <span\n data-pin-intersection\n data-pin-dim-count={pinDimCount}\n data-pin-intersection-count={matchAliases.length}\n data-pin-intersection-empty={isEmpty ? 'true' : 'false'}\n data-pin-intersection-aliases={matchAliases.join(',')}\n /* Round 235 / Loop: pin-intersection chip joins the\n info-density tabular-nums sweep. The chip has TWO\n digits visible at once — '{pinDimCount} pins ·\n {matchAliases.length}' — and the matchAliases count\n in particular rolls frequently as filters tighten /\n widen against the live fleet. font-mono already\n makes the digits uniform-ish, but tabular-nums\n further locks digit width within the mono cell so\n the gap between 'pins' and '·' stays stable, and\n the chip's overall width doesn't bump when either\n number changes. 9th surface in the sweep — the\n third and last HTML chip surface, completing\n coverage across chip-row + vendor-row + pin-\n intersection. */\n className=\"hidden sm:inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono tabular-nums text-xs border anet-fade-in anet-topo-chip-focus\"\n title={tooltip}\n /* Round 236 / Loop: smooth the empty/non-empty colour\n crossing. Pre-R236 the chip snap-flipped between\n slate-on-slate (non-empty filter intersection) and\n amber-on-amber (empty intersection — the 'your\n pinned filters cancel out' warning). bg, color,\n and borderColor (which inherits currentColor) all\n changed in one frame. R236 adds a 200ms ease-out\n transition on all three so when a filter tightens\n matches across the 0-boundary the chip eases\n through the colour shift instead of snapping. Same\n 200ms cadence the other chip-row members use\n (R201 working/online tint, R193 active-links\n tint, R202 vendor letter color-mix). One more\n surface where colour state-changes ease rather\n than snap — consistent with the topology's\n broader transitions vocabulary. */\n style={{\n background: isEmpty\n ? (isLight ? '#d97706' + '14' : '#fbbf24' + '1f')\n : (isLight ? '#94a3b814' : '#94a3b81f'),\n color: isEmpty\n ? emptyColor\n : (isLight ? '#475569' : '#9ca3af'),\n borderColor: 'currentColor',\n transition: 'background-color 200ms ease-out, color 200ms ease-out, border-color 200ms ease-out',\n }}\n >\n <span>\n <span className=\"hidden sm:inline opacity-70\" data-pin-intersection-prefix>match: </span>\n {/* Round 324 / Loop: pin-intersection chip carries TWO\n numeric counts in one breath — pinDimCount (\"how\n many filter pins are active\") and matchAliases.\n length (\"how many aliases land in the intersection\n after pins compose\"). Both jitter on digit-width\n crossings (1→10 etc) without tabular-nums even\n under font-mono. Pre-R324 a fleet busying up so\n one dimension flips from 0→non-zero (chip mounts\n via R237 always-mount opacity gate) AND the match\n count digit ticks 9→10 simultaneously visibly\n jolted the trailing `× pins` / ` × ` segments.\n Two dedicated tabular-nums spans (one per count)\n lock both digit slots so the chip's text geometry\n stays planted through both crossings. 10th\n surface in the info-density tabular-nums sweep\n after R323 filter pin pill counts (R64/R89/R119/\n R150 pin-pill family parity now complete with\n this composed-pin sibling). data-pin-intersection-\n count-* attrs expose both spans for tests. */}\n {/* Round 341 / Loop: middle \" pins\" unit word\n previously sat as a bare text node between the\n two count spans, while the matches-count span\n already carried opacity-0.7 (R335 + R324 era).\n The pinDimCount span is prominent and the\n matches count is recessive — but the literal\n \" pins\" was at FULL opacity, breaking the\n chip-internal hierarchy unified across R333/\n R335/R336/R337/R338/R340. R341 wraps \" pins\"\n in an opacity-0.7 span so the chip reads:\n pinDimCount (prominent value)\n \" pins\" (recessive unit)\n \" · {N}\" (recessive count)\n Three-tier hierarchy on a single chip; 7th\n surface in the chip-internal-hierarchy arc. */}\n <span className=\"tabular-nums\" data-pin-intersection-count-dims>{pinDimCount}</span><span className=\"opacity-70\" data-pin-intersection-unit> pins</span><span className=\"opacity-70 tabular-nums\" data-pin-intersection-count-matches> · {matchAliases.length}</span>\n {/* Round 237 / Loop: ⚠ warning glyph picks up the\n always-mount-opacity-gate idiom. Pre-R237 the\n glyph was conditionally rendered on isEmpty,\n snap-mounting when filter intersection\n narrowed to 0 AND introducing a layout shift\n (ml-1 margin appears alongside the glyph,\n widening the chip by ~16px). The R236 color\n easing made the colour crossing smooth but\n the glyph still pop-jumped, breaking the\n polish that R236 just installed at this\n same chip.\n\n Always-mount the glyph with opacity gated by\n isEmpty + the same 200ms ease-out that R236\n uses on the chip's colour transition. Now the\n whole isEmpty crossing — bg, color, border,\n AND glyph visibility — eases as one\n coordinated 200ms event. ml-1 margin is\n reserved permanently, so the chip width\n stays stable through the crossing (no\n layout-shift jank against neighbouring\n chips). data-pin-intersection-warning attr\n surfaces the visibility state for test\n introspection. 11th surface in the always-\n mount-opacity-gate family (R181 / R182 /\n R183 / R213 ×2 / R214 / R215 / R221 / R222 /\n R223 / R237). */}\n <span\n className=\"ml-1\"\n aria-hidden\n data-pin-intersection-warning={isEmpty ? 'true' : 'false'}\n style={{ opacity: isEmpty ? 1 : 0, transition: 'opacity 200ms ease-out' }}\n >⚠</span>\n </span>\n </span>\n );\n })()}\n {/* Round 281 / Loop: vendor letters chip threshold tightens\n from >1 to >2 per 减法 cut #7. Pre-R281 the chip showed\n whenever ≥2 vendor types existed in the fleet — for a\n typical demo (claude + 1 other = 2 types), the chip\n rendered \"A:N C:M\" adding ~50-80px to the chip-row width.\n Tightening to >2 keeps the chip useful for fleets with\n ACTUAL vendor diversity (3+ types) where the\n composition matters at a glance, but hides it for the\n common 1-2 vendor case where the info is low-signal.\n Continues the R275-R280 simplification arc. */}\n {vendorDist.length > 2 && (\n <span\n className=\"hidden sm:inline-flex items-center gap-2 px-2.5 py-1 rounded-md bg-gray-500/10 text-gray-400 border border-gray-500/20 font-mono\"\n title=\"Hover to highlight; click to pin\"\n >\n {vendorDist.map(v => {\n const isPinned = pinnedVendor === v.initial;\n // R88: click toggles a sticky filter the same way R60\n // pressure-bar segments toggle pinnedStatus. Visual\n // mirror = inset boxShadow using the vendor's own\n // colour, so each pinned letter sings in its own hue\n // (Anthropic green / OpenAI cyan / 书 blue / ?).\n // R101: tooltip lists the aliases that use this vendor —\n // completes the info-density triple started by R97 pills,\n // R98 node titles, R99 group-label titles. Anywhere the\n // UI shows \"A:3\" should hover-explain which 3.\n const aliases = [...onlineNodes, ...offlineNodes]\n .filter(s => {\n const vid = vendorForModel(s.model);\n return (vid.id === 'unknown' ? '?' : vid.initial) === v.initial;\n })\n .map(s => s.alias);\n const preview = aliases.slice(0, 8).join(', ');\n const suffix = aliases.length > 8 ? ` + ${aliases.length - 8} more` : '';\n const tooltip = isPinned\n ? `${preview}${suffix} — click again or Esc to clear`\n : `${preview}${suffix} — click to pin`;\n return (\n <span\n key={v.initial}\n role=\"button\"\n tabIndex={0}\n aria-pressed={isPinned}\n /* Round 234 / Loop: vendor letter chip picks up\n tabular-nums to lock the ':N' suffix's digit\n width. Each vendor chip renders 'A:3', 'C:2',\n etc. inline at gap-0.5 — when one vendor's\n count rolls 9→10 the chip widens by the digit\n glyph delta and pushes downstream chips right,\n making the row visibly jitter. 8th surface\n in the info-density tabular-nums sweep,\n completing the HTML chip-side coverage after\n R232 working/online/active-links chips. The\n digit lives in the inner <span> at line 2194,\n but font-variant-numeric inherits, so applying\n it at the outer chip span reaches every\n descendant glyph for free. */\n /* Round 314 / Loop: vendor letter chip joins the\n R312-R313 'HTML-context data chip = font-medium'\n family. R313 weighted the 3 main chips\n (working/online/active-links); R314 closes the\n chip-row weight sweep by extending to the\n vendor letter chips ('A:N', 'O:N', '书:N',\n '?:N'). They display vendor-distribution\n data; same tier as the sibling data chips. */\n className=\"tabular-nums font-medium inline-flex items-baseline gap-0.5 px-1 rounded anet-topo-chip-focus\"\n data-vendor-letter={v.initial}\n data-vendor-letter-count={v.count}\n data-vendor-pinned={isPinned ? 'true' : 'false'}\n data-vendor-hovered={hoveredVendor === v.initial ? 'true' : 'false'}\n data-vendor-aliases={aliases.join(',')}\n title={tooltip}\n // R180: smooth-pin-mirror family — see working chip above.\n // Round 202 / Loop: vendor letter chip joins the \"hover\n // deepens own identity hue\" family R193/R195/R201 built\n // up across the rest of the chip row. Pre-R202 hovering\n // a vendor letter (A/C/G/K/书/?) fired R88 canvas dim\n // via setHoveredVendor, but the chip itself stayed at\n // bg=transparent — cause silent, effect loud. R202\n // tints the chip with its OWN vendor colour at 12%\n // alpha via color-mix() so each vendor's chip lights\n // up in its own hue (Anthropic green / OpenAI cyan /\n // 书 blue / ?). No layout shift: only background-color\n // changes, no border/padding swap. transition list\n // extends the existing R180 box-shadow 150ms with\n // background-color 200ms ease-out (same splice idiom\n // R201 used on the working/online chips). color-mix()\n // is supported Chrome ≥ 111 / Safari ≥ 16.2 / FF ≥ 113;\n // for older browsers the chip falls back to its idle\n // transparent bg (graceful degradation — the canvas-\n // dim effect still fires regardless).\n style={{\n cursor: 'pointer',\n backgroundColor: (hoveredVendor === v.initial && !isPinned)\n ? `color-mix(in srgb, ${v.color} 12%, transparent)`\n : 'transparent',\n boxShadow: isPinned\n ? `inset 0 0 0 1px ${v.color}, inset 0 0 0 2px rgba(255,255,255,0.45)`\n : undefined,\n transition: 'box-shadow 150ms ease-out, background-color 200ms ease-out',\n }}\n onMouseEnter={() => setHoveredVendor(v.initial)}\n onMouseLeave={() => setHoveredVendor(prev => prev === v.initial ? null : prev)}\n onClick={() => setPinnedVendor(prev => prev === v.initial ? null : v.initial)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setPinnedVendor(prev => prev === v.initial ? null : v.initial);\n }\n }}\n >\n <span style={{ color: v.color }}>{v.initial}</span>\n {/* Round 333 / Loop: vendor count suffix `:{N}` joins\n the R317 subordinate-text-lift family (gray-500 →\n gray-400) plus picks up tabular-nums for digit\n width-lock. Pre-R333 a vendor whose count\n crossed 9→10 widened the suffix and (since the\n parent chip has `px-2.5` padding but no fixed\n width) shifted the chip-row's downstream chips\n a couple px right. Tabular-nums locks the slot;\n gray-400 lifts the digit into the band where eye\n reads it as \"deliberate subordinate metadata\"\n rather than near-invisible chrome. data-vendor-\n letter-count exposes the span for tests. */}\n <span\n className=\"text-gray-400 tabular-nums\"\n data-vendor-letter-count-suffix\n >:{v.count}</span>\n </span>\n );\n })}\n </span>\n )}\n {/* Round 42 / Loop: extend active-links chip with the timestamp\n of the most-recent flow event. Tells the operator at a glance\n whether the topology is currently humming (last 30s) or has\n been quiet for a while — the visual flow particles and\n edge brightness only show that there IS traffic, not when\n the last one was. Reuses Round 38's relativeAgo. */}\n {(() => {\n const recent = flowLinks.reduce<number | null>((acc, l) => {\n if (!l.last_at) return acc;\n const t = parseHubTime(l.last_at);\n if (t === null) return acc;\n return acc === null || t > acc ? t : acc;\n }, null);\n const rel = recent !== null ? relativeAgo(new Date(recent).toISOString()) : null;\n // R114: tooltip lists the actual flows. Closes the\n // info-density sweep on the last chip-row hover surface\n // (R97-R113 covered everything else). Format:\n // \"alpha→beta (3), gamma→delta (1) — hover brightens all\"\n // Truncates at 6 flows with \"+N more\" so a busy fleet\n // doesn't paint a tall tooltip; the recent-signal panel\n // already shows the top 3 in detail.\n const flowList = flowLinks\n .slice(0, 6)\n .map(l => `${l.from}→${l.to} (${l.count})`)\n .join(', ');\n const flowSuffix = flowLinks.length > 6 ? ` + ${flowLinks.length - 6} more` : '';\n // R136: the chip already had cursor:pointer when flowLinks\n // > 0 (line 1877) — but no onClick was wired. The cursor\n // lied; users got the affordance signal with no follow-\n // through. Wire it to /messages, mirroring R133's footer-\n // nav idiom. Hover (R77) keeps its semantic \"preview all\n // flows on canvas\"; click is the action \"open the full\n // list\". Two distinct gestures, both meaningful. The\n // tooltip grows a \"click to open\" tail when interactive.\n // Drop the chip out of click territory entirely when\n // flowLinks is empty — no flows = no list to open.\n const isInteractive = flowLinks.length > 0;\n const tooltip = !isInteractive\n ? undefined\n : `${flowList}${flowSuffix} — hover brightens all · click to open /messages`;\n return (\n // Round 193 / Loop: the chip itself adopts a subtle cyan\n // tint on its own hover, mirroring the cyan flowEdge\n // highlight it fires on the canvas. Pre-R193 the gesture\n // was visually asymmetric:\n // hover this chip → canvas edges brighten (cyan)\n // chip itself → stays gray (silent)\n // The *cause element* (chip) gave no response while the\n // *effect element* (canvas edges) painted loud. Same\n // pin-mirror logic R165 / R180 use on the four other\n // chip-row surfaces — the chip and the canvas edge it\n // pins should speak the same color vocabulary. Tailwind\n // :hover variant cyan-500/10 bg + cyan-500/30 border +\n // cyan-200 text matches the same palette R178/R163 use\n // for the active chrome buttons. transition-colors\n // duration-200 blends the swap smoothly. Hover variant\n // only attaches when isInteractive — a chip showing\n // \"0 active links\" has no list to open and should\n // stay gray. data-active-links-clickable already\n // exposes that gate to tests.\n <span\n // Round 206 / Loop: extend the R204/R205 empty-recede\n // family to the active-links chip. Pre-R206 \"0 active\n // links\" rendered at the same bg-gray-500/10 + full\n // opacity as \"12 active links\" — same eye-no-signal\n // problem the legend count (R204) and working/online\n // chips (R205) just solved at their own grain levels.\n // Inline opacity 0.5 when !isInteractive (flowLinks=0)\n // joins R136's already-removed cursor + R114's tooltip-\n // text gate to give the empty state visual + interactive\n // + affordance signals in lockstep.\n // Tailwind transition-colors duration-200 on className\n // would be overridden by the inline transition list, so\n // we replicate color/bg/border transitions inline\n // alongside the new opacity 200ms — same splice idiom\n // R201 used on the working/online chips.\n /* Round 232 / Loop: tabular-nums on active-links chip\n (third chip in the row — matches working + online\n chip treatment so all three digits in the chip row\n stay width-stable across counter crossings). */\n className={`tabular-nums font-medium hidden sm:inline px-2.5 py-1 rounded-md border anet-topo-chip-focus ${\n isInteractive\n ? 'bg-gray-500/10 text-gray-400 border-gray-500/20 hover:bg-cyan-500/10 hover:text-cyan-200 hover:border-cyan-500/30'\n : 'bg-gray-500/10 text-gray-400 border-gray-500/20'\n }`}\n data-active-links-chip\n data-active-links-flow-count={flowLinks.length}\n data-active-links-clickable={isInteractive ? 'true' : 'false'}\n data-active-links-empty={isInteractive ? 'false' : 'true'}\n title={tooltip}\n role={isInteractive ? 'link' : undefined}\n tabIndex={isInteractive ? 0 : undefined}\n style={{\n cursor: isInteractive ? 'pointer' : undefined,\n opacity: isInteractive ? 1 : 0.5,\n transition: 'color 200ms ease-out, background-color 200ms ease-out, border-color 200ms ease-out, opacity 200ms ease-out',\n }}\n onMouseEnter={() => { if (isInteractive) setHoveredActiveLinks(true); }}\n onMouseLeave={() => setHoveredActiveLinks(false)}\n onClick={() => { if (isInteractive) router.push('/messages'); }}\n onKeyDown={(e) => {\n if (!isInteractive) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n router.push('/messages');\n }\n }}\n >\n {/* R338 — active-links chip digit/unit split, completes\n the 5th chip surface in the R333/R335/R336/R337\n chip-internal-hierarchy arc. data-active-links-\n chip-unit exposes the unit span for tests. */}\n {flowLinks.length}<span className=\"opacity-70\" data-active-links-chip-unit> active link{flowLinks.length === 1 ? '' : 's'}</span>\n {rel ? (() => {\n // Round 161 / Loop: extend R160's recency-pip\n // vocabulary up one scope — from per-flow row to\n // fleet-aggregate chip. The chip already shows\n // recency in text (\"last 5s ago\"); the \" · \"\n // separator bullet was dead gray. Color the\n // bullet by freshness using the same alpha\n // ramp R160 uses on recent-signal rows:\n // ageSec ≤ 30 → 1.0 (fully fresh)\n // 30-300s → smooth decay 1.0 → 0.25\n // > 300s → 0.25 (stale floor)\n // Cyan bullet pulse when fresh + gray text tail\n // = \"is the network firing right now\" readable\n // at a glance, without parsing the timestamp\n // numerals. Same vocabulary the canvas uses\n // (R10 edge fade) and the recent-signal panel\n // uses (R160 row pip) — three nested scopes\n // now share one freshness ladder.\n const ageSec = recent !== null\n ? Math.max(0, (Date.now() - recent) / 1000)\n : 999;\n const alpha = ageSec <= 30\n ? 1\n : ageSec <= 300\n ? 1 - ((ageSec - 30) / 270) * 0.75\n : 0.25;\n // Cyan dark / teal light to match palette legendAccent.\n const dotColor = isLight\n ? `rgba(13, 148, 136, ${alpha.toFixed(2)})`\n : `rgba(34, 211, 238, ${alpha.toFixed(2)})`;\n // Round 342 / Loop: active-links chip freshness suffix\n // wrapper text-gray-500 → text-gray-400 (R317\n // subordinate-text-lift family applied to chrome\n // inactive Layout toggle + R333 vendor count suffix).\n // The \"last 5s ago\" suffix is chip-subordinate\n // metadata; gray-500 sat near-invisible against the\n // chip's outer color, gray-400 lifts it into the band\n // where the eye reads it as deliberate freshness\n // annotation. The freshness DOT keeps its own inline\n // color: dotColor — the lift only affects the trailing\n // literal \"last {rel}\" text.\n return (\n <span className=\"text-gray-400\">\n <span\n data-active-links-freshness-dot\n data-active-links-freshness-alpha={alpha.toFixed(2)}\n style={{\n color: dotColor,\n fontWeight: alpha > 0.7 ? 700 : 400,\n transition: 'color 200ms ease-out',\n }}\n >{' · '}</span>\n last {rel}\n </span>\n );\n })() : null}\n </span>\n );\n })()}\n <FreshnessChip sessions={sessions} />\n </div>\n </div>\n\n <div\n ref={containerRef}\n /* Round 330 / Loop (milestone): canvas wrapper rounded-lg\n → rounded-xl (8px → 12px corner radius). The biggest\n single surface on the dashboard by pixel area now reads\n as modern-SaaS-contemporary rather than 2020-conservative\n — same 4px bump R197 applied to the legend swatch and\n R295 applied to the title-block crescent. R330 ports\n the gesture to the OUTER envelope.\n Inner content (SVG viewBox 1000×680) sits behind\n `overflow-hidden`, so corner-radius change only affects\n the wrapper's own paint area and the shadow contour;\n the topo-overlap-test reads SVG-internal geometry and is\n unaffected. R254 background-color / R254 border-color /\n R263 box-shadow transitions all carry through unchanged.\n Marks R330 milestone of 5 rounds (R326-R330) of layout-\n geometry polish (gap-tier + crescent fade + trailer\n compensator + corner radius). */\n className={`relative overflow-hidden rounded-xl border shadow-2xl ${isLight ? 'shadow-zinc-900/5' : 'shadow-cyan-950/30'} ${isFullscreen ? 'flex items-center justify-center' : ''}`}\n data-topo-wrapper\n /* Round 254 / Loop: top-level TopoGraph wrapper gains theme-\n toggle transition. This is the BIGGEST theme-driven surface\n on the dashboard by pixel area — pal.containerBg fills the\n entire visible canvas area (cyber #080814 ↔ light #ffffff),\n and pal.containerBorder rims it. Pre-R254 every inner\n element eased through theme but the outer wrapper hard-cut,\n visually anchoring the snap. R253 declared\n \"no visible snap remains\" prematurely — this wrapper was\n the largest holdout. 200ms ease-out matches the panel\n treatment (R247) so wrapper + panels ease as one unit.\n\n Round 263 / Loop: close R254's holdover gap — the wrapper's\n shadow-2xl + theme-conditional `shadow-{color}/{opacity}`\n Tailwind class (cyber `shadow-cyan-950/30` ↔ light\n `shadow-zinc-900/5`) ALSO changes on theme toggle, but the\n inline transition list only covered background-color +\n border-color. Result: every inner element eased through\n theme, the wrapper bg/border eased, but the wrapper's\n DROP-SHADOW snapped — a subtle but real holdover from\n R254's \"TRULY complete\" claim. Adding `box-shadow 200ms\n ease-out` to the transition list catches the className-\n driven box-shadow swap (CSS transition on box-shadow eases\n the shadow property even when its color comes from a\n Tailwind class change, because the property itself is\n transition-eligible regardless of source). */\n style={{\n background: pal.containerBg,\n borderColor: pal.containerBorder,\n transition: 'background-color 200ms ease-out, border-color 200ms ease-out, box-shadow 200ms ease-out',\n }}\n >\n {/* Round 265 / Loop: top-rail (1px-tall colored line at the top\n of the canvas wrapper) picks up theme-toggle transition.\n Pre-R265 the className `bg-gradient-to-r ${pal.topRail\n Gradient}` was theme-conditional — cyber `via-cyan-400/70`\n ↔ light `via-emerald-500/40` — but no inline transition,\n so the rail SNAPPED on theme flip while the wrapper bg\n (R254) + border (R254) + shadow (R263) all eased. The top-\n rail is the THIN BRIGHT LINE that visually anchors the\n canvas top edge — its hard color flip was a small but real\n theme-snap that broke the otherwise-eased canvas envelope.\n transition: background-image catches the className-driven\n gradient swap; Chrome ≥ 89 / Safari ≥ 14.1 / FF ≥ 96\n interpolate linear-gradients with matching stop structures\n (both gradients are `from-transparent via-X to-transparent`\n → same 3-stop layout). data-topo-top-rail makes the probe\n deterministic. */}\n <div\n className={`absolute inset-x-0 top-0 h-px bg-gradient-to-r ${pal.topRailGradient}`}\n data-topo-top-rail\n style={{ transition: 'background-image 200ms ease-out' }}\n />\n\n {/* Round 158 / Loop: give the canvas SVG itself an accessible\n name + role description. R151-R157 added a11y to every\n interactive surface inside the canvas (nodes, group labels,\n badges, rows, minimap, chrome buttons) — but the canvas\n container itself was a nameless 1000×680 box. A screen\n reader that hit the SVG before tab-diving into its\n children heard nothing identifying it as \"the topology\".\n aria-roledescription gives it a meaningful announcement;\n aria-label provides a live snapshot of the network\n (online / working / active links / offline) plus the two\n canvas-scope gestures (Tab + double-click). Default SVG\n role is graphics-document so children remain navigable —\n we deliberately don't override role with \"img\" which\n would flatten the SVG to a single opaque image. */}\n <svg\n ref={svgRef}\n viewBox=\"0 0 1000 680\"\n className=\"w-full h-auto block\"\n preserveAspectRatio=\"xMidYMid meet\"\n aria-roledescription=\"agent network topology\"\n aria-label={(() => {\n const online = onlineNodes.length;\n const working = workingCount;\n const offline = offlineNodes.length;\n const flows = flowLinks.length;\n const parts: string[] = [];\n parts.push(`${online} agent${online === 1 ? '' : 's'} online`);\n if (working > 0) parts.push(`${working} working`);\n if (offline > 0) parts.push(`${offline} offline`);\n parts.push(`${flows} active link${flows === 1 ? '' : 's'}`);\n return `Agent network topology — ${parts.join(' · ')}. Tab to navigate nodes, double-click canvas to reset view.`;\n })()}\n data-topo-canvas-aria\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerLeave={onPointerUp}\n // Round 41 / Loop: the reset-button title (R22) and the Help\n // overlay (R25) both advertise \"double-click the canvas to\n // reset\", but the handler was never actually wired. The text\n // was lying. Wire it here, guarded so dbl-clicking a node\n // (which would also trigger the SVG-level handler via event\n // bubbling) doesn't unexpectedly reset the view on the user.\n onDoubleClick={(e) => {\n const t = e.target as Element | null;\n if (t?.closest('g[data-node]')) return;\n resetView();\n }}\n style={{ cursor: isPanning ? 'grabbing' : 'grab', touchAction: 'none' }}\n >\n <defs>\n <linearGradient id=\"topo-panel\" x1=\"0\" x2=\"1\" y1=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor={pal.panelStops[0]} />\n <stop offset=\"48%\" stopColor={pal.panelStops[1]} />\n <stop offset=\"100%\" stopColor={pal.panelStops[2]} />\n </linearGradient>\n <radialGradient id=\"topo-radar\" cx=\"50%\" cy=\"50%\" r=\"55%\">\n <stop offset=\"0%\" stopColor={pal.radarStops[0].color} stopOpacity={pal.radarStops[0].opacity} />\n <stop offset=\"45%\" stopColor={pal.radarStops[1].color} stopOpacity={pal.radarStops[1].opacity} />\n <stop offset=\"100%\" stopColor={pal.radarStops[2].color} stopOpacity={pal.radarStops[2].opacity} />\n </radialGradient>\n {!isLight && (\n <filter id=\"topo-glow\">\n <feGaussianBlur stdDeviation=\"4\" result=\"blur\" />\n <feMerge>\n <feMergeNode in=\"blur\" />\n <feMergeNode in=\"SourceGraphic\" />\n </feMerge>\n </filter>\n )}\n {/* R142 / Loop: drop-shadow filter for group-box hover-lift.\n Mirrors R135's panel hover-elevation idiom but applied\n at the per-group canvas level. R68 already gives a\n hovered/pinned group box a solid accent stroke; R142\n adds a soft outward shadow on top so the box visually\n \"rises off the canvas\" when selected — same visual\n vocabulary as the panels + the Overview KPI cards\n (R18). Theme-aware flood: darker shadow on cyber so\n it reads above the dark canvas, lighter for light\n theme. Bounding box of the group box is unchanged —\n filters only affect paint area, not bbox geometry, so\n the overlap-test invariant is preserved. */}\n <filter id=\"topo-groupbox-lift\" x=\"-10%\" y=\"-10%\" width=\"120%\" height=\"120%\">\n <feDropShadow\n dx=\"0\" dy=\"3\" stdDeviation=\"4\"\n floodColor={isLight ? '#0f172a' : '#000000'}\n floodOpacity={isLight ? 0.18 : 0.55}\n />\n </filter>\n {/* Round 16 / Loop: 3-tier flow-link arrow markers.\n The single marker had `markerUnits` defaulting to\n `strokeWidth`, so heavy edges (stroke=7) rendered 35-user-\n unit arrowheads — visually dominant, the head outweighed\n the line. Switching to `userSpaceOnUse` decouples arrow\n size from stroke; binning by count gives a clearer\n hierarchy than continuous linear scaling:\n s (count 1-2) → 12 user units\n m (count 3-4) → 16 user units (alias for `topo-arrow`)\n l (count 5+) → 22 user units\n `topo-arrow` stays bound to the medium tier so the legend\n swatch (line ~1500) renders without change. */}\n {[\n { id: 'topo-arrow-s', size: 12 },\n { id: 'topo-arrow', size: 16 },\n { id: 'topo-arrow-l', size: 22 },\n ].map(m => (\n <marker\n key={m.id}\n id={m.id}\n viewBox=\"0 0 10 10\"\n refX=\"8\"\n refY=\"5\"\n markerWidth={m.size}\n markerHeight={m.size}\n markerUnits=\"userSpaceOnUse\"\n orient=\"auto-start-reverse\"\n >\n <path d=\"M 0 0 L 10 5 L 0 10 z\" fill={pal.arrowFill} />\n </marker>\n ))}\n {/* Round 45: radial radar sweep gradient — bright at center\n (where it meets the hub) fading to leading edge. */}\n <radialGradient id=\"topo-sweep\" cx=\"0%\" cy=\"50%\" r=\"100%\">\n <stop offset=\"0%\" stopColor={isLight ? '#0d9488' : '#22d3ee'} stopOpacity={isLight ? 0.18 : 0.32} />\n <stop offset=\"70%\" stopColor={isLight ? '#0d9488' : '#22d3ee'} stopOpacity={isLight ? 0.10 : 0.18} />\n <stop offset=\"100%\" stopColor={isLight ? '#0d9488' : '#22d3ee'} stopOpacity=\"0\" />\n </radialGradient>\n </defs>\n\n {/* panel backdrop stays fixed — panning never reveals empty canvas */}\n <rect width=\"1000\" height=\"680\" fill=\"url(#topo-panel)\" />\n\n {/* Round 103 (issue #81): everything inside this <g> zooms + pans\n together. transform order = translate then scale.\n Round 168 / Loop: smoothView arms a one-shot transition on\n the transform attribute, active only when resetView/fitView\n fires. Pan (R103 pointer drag) and wheel zoom never set the\n flag, so they stay snappy with no lag. Pressing `0`, `f`,\n clicking the hub (R52), or chrome reset/fit buttons triggers\n a 300ms ease-out glide instead of a jolt. Respects prefers-\n reduced-motion via the R29 globals.css blanket override that\n neutralises transition-duration universally. */}\n <g\n transform={`translate(${view.x} ${view.y}) scale(${view.zoom})`}\n data-topo-viewport\n data-topo-viewport-smooth={smoothView ? 'true' : 'false'}\n data-topo-viewport-layout-switching={layoutSwitching ? 'true' : 'false'}\n data-topo-viewport-nodesize-switching={nodeSizeSwitching ? 'true' : 'false'}\n style={{\n // R170 / Loop: opacity always carries a transition so the\n // layout-switch crossfade fires cleanly. R168 smoothView\n // transition on transform is added only when armed (pan\n // and wheel zoom MUST stay snappy). The two arming flags\n // compose without conflict — different visual axes.\n // R171: nodeSizeSwitching shares the same opacity-dim\n // pathway as layoutSwitching — clicking S/M/L in the\n // chrome triggers the same soft-blink masking. ORing\n // both flags keeps the opacity expression simple while\n // the two distinct data-* attributes let tests\n // disambiguate which gesture armed the crossfade.\n transition: smoothView\n ? 'opacity 250ms ease-out, transform 300ms ease-out'\n : 'opacity 250ms ease-out',\n opacity: (layoutSwitching || nodeSizeSwitching) ? 0.45 : 1,\n }}\n >\n {/* Issue #87: radar/ring ambiance renders only in ring layout —\n grid mode drops it so the concentric rings don't sit behind a\n rectangular grid. */}\n {layout === 'ring' && (<>\n {/* R52: radar bg is pure decoration — drop its pointer events so\n the hub <g> under it (and any future inner-disk affordances)\n can receive clicks. Previously the r=330 disc intercepted\n the hub click outright. */}\n <circle cx={cx} cy={cy} r=\"330\" fill=\"url(#topo-radar)\" style={{ pointerEvents: 'none' }} />\n\n {/* Round 45: subtle star field — deterministic dots scattered\n across the canvas give the radar bg some depth. Skipped on\n light theme so the white surface stays clean.\n Round 291 / Loop: starfield dot count 28 → 14 (50%\n reduction). Post-R290 inner radar ring retirement the\n canvas has cleared meaningfully — sweep + 3 radar rings\n + tier guides + nodes + edges are doing the visual work.\n The starfield's role is atmospheric depth, not\n information; cutting density by half preserves the\n \"space/radar\" feel while removing decoration the eye\n has to skip. Same R275-R281 减法 family idiom as the\n orbit / halo / spoke retirements; same R290 pivot back\n to subtractive register. data-topo-starfield-dot\n attribute makes the dots probe-able for the regression\n test. */}\n {!isLight && (\n <g opacity=\"0.5\" style={{ pointerEvents: 'none' }} data-topo-starfield>\n {Array.from({ length: 14 }).map((_, i) => {\n // Deterministic pseudo-random scatter so positions are\n // stable between renders (no JS hydration mismatch).\n const seed = i * 9301 + 49297;\n const x = ((seed * 13) % 1000);\n const y = ((seed * 7) % 680);\n const r = (i % 3 === 0) ? 1.2 : 0.7;\n return <circle key={i} cx={x} cy={y} r={r} fill=\"#a5b4fc\" opacity={0.35 + (i % 4) * 0.05} data-topo-starfield-dot={i} />;\n })}\n </g>\n )}\n\n {/* Round 45: rotating radar sweep — a 40° wedge with a soft\n leading-edge gradient. Slow 6s rotation reads as a radar\n scan without being noisy. Inline transform-origin on the\n <g> wrapper ensures Chrome / Firefox rotate around (cx,cy)\n instead of the SVG viewBox corner.\n\n v0.10.0 Hero 3 Wave 1 / RFC §3.B (Vincent 5222 holdover):\n sweep arc retired. The diagonal rotating wedge competes\n with working-halo SMIL, hub busyness breath, and edge\n flow animation — on a 16:9 Twitter screenshot it reads\n 'wow lots of motion' rather than 'agents communicating'.\n Same idiom as R278/R279/R280 retirements — `false &&`\n short-circuits the IIFE so it's a one-line rollback. */}\n {false && (() => {\n // R146: radar sweep rotation buckets on workingCount, joining\n // R84 hub breath / R131 outer orbit / R132 group march /\n // R145 idle spokes as the 5th and final layer in the busyness-\n // driven motion family. 8 / 6 / 4 / 3 seconds — same 0 / 1-2 /\n // 3-5 / 6+ thresholds R84 uses. \"Busier fleet = more frequent\n // scans\" feels semantically right for a radar idiom. R45\n // baseline 6s sits at bucket 1 so the calm/busy spread bracks\n // around the historical default.\n const busy = workingCount === 0 ? 0\n : workingCount <= 2 ? 1\n : workingCount <= 5 ? 2\n : 3;\n const sweepDur = [8, 6, 4, 3][busy];\n return (\n <g\n style={{\n transformOrigin: `${cx}px ${cy}px`,\n transformBox: 'view-box',\n pointerEvents: 'none',\n // CSS var consumed by `.anet-topo-sweep` (line 848 of\n // globals.css). React's CSSProperties type doesn't model\n // custom properties → cast through Record<string, string>.\n ...({ ['--sweep-dur']: `${sweepDur}s` } as Record<string, string>),\n } as React.CSSProperties}\n className=\"anet-topo-sweep\"\n opacity={isLight ? 0.7 : 1}\n data-topo-sweep-bucket={busy}\n data-topo-sweep-dur={sweepDur}\n >\n <path\n d={`M ${cx} ${cy} L ${cx + 330} ${cy} A 330 330 0 0 0 ${cx + 330 * Math.cos(-Math.PI / 4.5)} ${cy + 330 * Math.sin(-Math.PI / 4.5)} Z`}\n fill=\"url(#topo-sweep)\"\n />\n </g>\n );\n })()}\n\n {/* radar rings — pure decoration at fixed radii, independent of\n node positions so the radar aesthetic is preserved across tier\n changes.\n Round 290 / Loop: drop the innermost radar ring at r=90.\n That ring sat ~66px outside the hub (hub radius 24, halo\n r=18), in the exact zone R276 (orbit particles) / R278\n (working halo) / R280 (backdrop spokes) cleared during\n the R275-R281 减法 arc. Post-cleanup the lone r=90 ring\n read as a leftover decorative loop hugging the hub — a\n visual element with no remaining sibling to anchor.\n Dropping it returns to the subtractive register after\n R282-R289's 8 加法 rounds and lets the hub breathe. The\n outer three rings (170 / 250 / 330) still carry the\n radar aesthetic across the canvas. New data-topo-radar-\n ring attribute exposes each remaining ring radius for\n test probing. */}\n {[170, 250, 330].map(radius => (\n <circle\n key={radius}\n cx={cx} cy={cy} r={radius}\n fill=\"none\" stroke={pal.ringStroke} strokeWidth=\"1\"\n opacity={isLight ? 0.6 : 0.35}\n data-topo-radar-ring={radius}\n />\n ))}\n\n {/* Round 54 / Loop: tier-radius guide rings. The radar rings above\n are decorative and don't match the actual tier radii nodes sit\n on (single 220 / dual 175,260 / triple 145,215,285). Drawing\n a faint dashed ring at each ACTIVE tier radius lets the eye\n anchor \"this is the inner / outer ring\" without inferring from\n node spacing. Picked based on online node count so only the\n tiers currently in use draw — empty tiers stay quiet. pointer-\n events:none so they never intercept hub or node clicks. The\n 0.7 stroke + dashed pattern reads as guide, not feature. */}\n {(() => {\n const tierRadii = onlineNodes.length > onlineTripleThreshold\n ? [onlineTripleInnerR, onlineTripleMidR, onlineTripleOuterR]\n : onlineNodes.length > onlineTierThreshold\n ? [onlineInnerRadius, onlineOuterRadius]\n : onlineNodes.length > 0\n ? [onlineRadius]\n : [];\n // Round 92 / Loop: tier-ring occupancy. R54 drew the guide\n // rings at fixed opacity regardless of how many nodes lived\n // on each tier. With pinned filters dimming most nodes to\n // 0.28, the ring at a deserted tier looked identical to a\n // crowded one — wasting a free piece of canvas. Count\n // online nodes whose hub-distance falls within ±15 px of\n // each ring (15 px = half the inter-tier gap, so each\n // node is assigned to exactly one ring). Empty tier → skip\n // entirely. Crowded tier → stronger opacity, says \"look\n // here\". Buckets so the ladder feels intentional, not\n // jittery as one node migrates between tiers.\n const occupancyOf = (r: number) => onlineNodes.reduce((acc, s) => {\n const p = nodePositions[s.alias];\n if (!p) return acc;\n const d = Math.hypot(p.x - cx, p.y - cy);\n return Math.abs(d - r) < 15 ? acc + 1 : acc;\n }, 0);\n // Round 93 / Loop: when any pin is active, tint the tier\n // rings to the legend accent so the spatial guide visually\n // shares the canvas's \"filtered mode\" colour. The chip\n // row already says WHICH filter is on (pills + letter\n // mirror); rings answering \"we're filtered\" reinforces\n // the state when the eye is on the canvas, not the chip\n // row. Composes cleanly with R92 occupancy — same opacity\n // bucket logic; just the stroke colour swaps.\n const anyPin = !!(pinnedStatus || pinnedGroup || pinnedVendor);\n const tierStroke = anyPin ? pal.legendAccent : pal.ringStroke;\n return tierRadii.map((r, tierIdx) => {\n const n = occupancyOf(r);\n if (n === 0) return null;\n const bucket = n <= 2 ? 0 : n <= 6 ? 1 : 2;\n const opLight = [0.24, 0.36, 0.50][bucket];\n const opDark = [0.32, 0.46, 0.62][bucket];\n // Round 174 / Loop: tier guide rings fade-in alongside\n // the R9/R72/R172/R173 first-paint wave. Ring layout's\n // structural scaffolding (R54 dashed concentric guides)\n // was the last instant-pop element after R173 closed\n // group boxes in grid. Same vocabulary — .anet-fade-in\n // mount-once CSS animation + per-ring stagger 60ms ×\n // tierIdx (cap 8). Tier rings are at most 3 (single /\n // dual / triple), so the visible range is 0-120ms.\n // Inner ring leads outward — emanates from the hub.\n // transition list grows `opacity 250ms ease-out` so the\n // post-animation snap from animation's end state (1) to\n // the bucket opacity (0.24-0.62) eases instead of cuts.\n // Same pattern node fade-in uses (R9 + transition-opacity\n // from R3 className). data-tier-fade-delay exposes the\n // computed delay for test probing.\n const fadeDelay = Math.min(tierIdx, 8) * 60;\n return (\n <circle\n key={`tier-${r}`}\n cx={cx} cy={cy} r={r}\n fill=\"none\"\n stroke={tierStroke}\n strokeWidth=\"0.7\"\n /* Round 303 / Loop: tier guide dashes tighten from\n \"2 8\" → \"2 6\" (8px gap → 6px gap). R54 set \"2 8\"\n to read as a faint hint behind everything else;\n after R290 (inner radar ring retired) + R291\n (starfield 50%) cleared the surrounding backdrop\n density, the tier guides carry more \"this is the\n ring nodes sit on\" visual responsibility. Pulling\n the gap from 8→6 puts dashes closer together so\n the ring reads as a clearer continuous mark\n rather than scattered dots, without bumping\n strokeWidth (0.7) or opacity (R92 bucketed). The\n 2px dash itself unchanged — same density signal\n per dash, just fewer-px space between them. */\n strokeDasharray=\"2 6\"\n opacity={isLight ? opLight : opDark}\n className=\"anet-fade-in\"\n style={{\n pointerEvents: 'none',\n transition: 'stroke 200ms ease-out, opacity 250ms ease-out',\n animationDelay: `${fadeDelay}ms`,\n }}\n data-tier-ring={r}\n data-tier-occupancy={n}\n data-tier-bucket={bucket}\n data-tier-tinted={anyPin ? 'true' : 'false'}\n data-tier-fade-delay={fadeDelay}\n />\n );\n });\n })()}\n\n {/* Round 50: 4 small particles slowly orbiting the outer ring\n (r=330). Each starts at a different angle (offset 0/0.25/0.5/0.75\n of the cycle) so they're evenly spaced. 16s per revolution is\n slow enough to feel ambient, not noisy. Skipped on light theme\n so the white surface stays clean.\n\n R131 / Loop: orbit period now buckets on workingCount,\n mirroring R84's hub-busyness breath cadence. An idle\n fleet keeps the original 16s \"calm sweep\"; as work\n accumulates the orbit subtly accelerates (capped at\n 10s so it never feels frantic). Two-layer motion\n coordination now: R84 breathes the hub, R131 spins\n the outer ring — both reading the same underlying\n \"is the network busy\" signal, both visible\n simultaneously without competing. Same bucket\n thresholds (0 / 1-2 / 3-5 / 6+) the R84 block uses\n at line ~2702 so the two cadences stay in sync if\n a future refactor shifts buckets.\n\n Round 276 / Loop: orbit particles DISABLED by default\n per Vincent 5214/5215-5217 visual-audit relay\n (clutter cleanup for Twitter screenshot). The 4\n particles encode workingCount busyness via speed\n (R131) + opacity (R216) — but that signal is\n ALREADY conveyed by:\n · hub halo opacity breath (R244, R84)\n · hub digit workingCount text (R130)\n · pressure-bar working/idle/offline ratio (R31)\n So orbit particles are info-redundant decoration:\n they don't add new signal, just add visual noise at\n the canvas outer edge. R276 gates the render block\n with `false &&` so the code stays (commented context\n + tests preserved for hypothetical rollback) but\n nothing renders. R131 busy-bucket constant + R216\n opacity-bucket constant are dead code post-R276 —\n acceptable since the family was R50/R131/R216 and\n R276 retires the family entirely. Net: 4 fewer\n moving dots on canvas. */}\n {false && !isLight && (() => {\n const busy = workingCount === 0 ? 0\n : workingCount <= 2 ? 1\n : workingCount <= 5 ? 2\n : 3;\n const dur = [16, 14, 12, 10][busy];\n // Round 216 / Loop: orbit particle opacity scales with busy\n // bucket alongside R131's speed scaling. Pre-R216 the\n // particles sat at flat opacity 0.9 regardless of fleet\n // workload — speed conveyed busyness but brightness was\n // mute. R216 layers brightness on top of speed so idle\n // fleets read calm (dim particles) and busy fleets read\n // bright (loud particles). Same R84 hub-breath /\n // R131 orbit-speed bucket thresholds (0 / 1-2 / 3-5 / 6+)\n // so the three motion layers (hub breath / orbit / group\n // march) plus the new brightness channel all derive from\n // one busyness metric. transition: opacity 300ms ease-out\n // matches R167 status-flip + R213 hub crossfade timing —\n // when first working node appears, hub focal point AND\n // outer ring particles brighten on the same 300ms beat.\n const orbitOpacity = [0.5, 0.7, 0.85, 1.0][busy];\n return [0, 0.25, 0.5, 0.75].map((phase, i) => (\n <g\n key={`orbit-${i}`}\n data-topo-orbit-bucket={busy}\n data-topo-orbit-dur={dur}\n data-topo-orbit-opacity={orbitOpacity}\n >\n <circle\n cx={cx + 330} cy={cy}\n r={i === 0 ? 2.8 : 2.2}\n fill=\"#22d3ee\"\n opacity={orbitOpacity}\n filter=\"url(#topo-glow)\"\n style={{ transition: 'opacity 300ms ease-out' }}\n >\n <animateTransform\n attributeName=\"transform\"\n type=\"rotate\"\n from={`${phase * 360} ${cx} ${cy}`}\n to={`${phase * 360 + 360} ${cx} ${cy}`}\n dur={`${dur}s`}\n repeatCount=\"indefinite\"\n />\n </circle>\n </g>\n ));\n })()}\n\n {/* Round 240 / Loop: extend R93 anyPin tinting from tier-rings\n to backdrop spokes. Pre-R240 the 6 radar-style spokes\n stayed at pal.ringStroke regardless of filter state,\n while R93 already shifted tier-rings to pal.legendAccent\n on any active pin. Result: ring scaffolding said\n 'filtered mode' but spoke scaffolding said 'rest' — the\n two halves of the canvas's spatial guide were out of\n sync. R240 ties them together; whole backdrop now reads\n as one filtered-mode-coloured unit when a pin is active.\n\n Same anyPin signal R93 uses (pinnedStatus || pinnedGroup\n || pinnedVendor). Same legendAccent tint colour. Same\n 200ms ease-out transition timing — pin a status, both\n tier-rings AND spokes ease to cyan together. */}\n {/* Round 280 / Loop: backdrop spokes RETIRED (R93 family with\n R240 tinting) per 减法 cut #6. The 6 radial lines at\n every 30° formed 12 rays from canvas center — even at\n opacity 0.18 (cyber) / 0.35 (light) they added explicit\n radial-line clutter behind the hub-and-spoke topology.\n The radial-gradient backdrop (topo-radar) ALREADY\n provides soft hub-centered glow; explicit lines on top\n were decorative density without structural signal that\n the topology itself doesn't already convey (hub at\n center + nodes on rings = radial structure inherent).\n `false &&` gates the render; code preserved for\n rollback. Same idiom as R276 orbit / R278 working halo\n / R279 ping+pulse retirements. */}\n {false && (() => {\n const anyPin = !!(pinnedStatus || pinnedGroup || pinnedVendor);\n const spokeStroke = anyPin ? pal.legendAccent : pal.ringStroke;\n return [0, 30, 60, 90, 120, 150].map(angle => (\n <line\n key={angle}\n x1={cx - 360 * Math.cos(angle * Math.PI / 180)}\n y1={cy - 360 * Math.sin(angle * Math.PI / 180)}\n x2={cx + 360 * Math.cos(angle * Math.PI / 180)}\n y2={cy + 360 * Math.sin(angle * Math.PI / 180)}\n stroke={spokeStroke}\n strokeWidth=\"1\"\n opacity={isLight ? 0.35 : 0.18}\n data-topo-spoke-angle={angle}\n data-topo-spoke-tinted={anyPin ? 'true' : 'false'}\n style={{ transition: 'stroke 200ms ease-out' }}\n />\n ));\n })()}\n </>)}\n\n {/* hub links — round 46: idle spokes now have animated\n stroke-dashoffset so dashes flow outward from the hub\n (\"command relay\" feel). Active spokes carrying live message\n flow stay as solid bright strokes.\n R145 / Loop: idle-spoke animation cadence buckets on\n workingCount, mirroring R84 hub-breath / R131 outer-\n ring orbit / R132 groupbox-march coordination. Same\n 0 / 1-2 / 3-5 / 6+ thresholds. Idle ↔ idle network\n has a slow \"command relay\" feel; as work accumulates\n the dashes accelerate outward, completing the 4th\n motion layer in the busyness-driven family:\n R84 hub breath (centre)\n R131 outer ring orbit (periphery)\n R132 groupbox march (per-team, grid layout)\n R145 idle-spoke flow (ring layout, hub→nodes)\n 4 surfaces, 1 signal. Same cadence ladder 2.8/2.4/2.0/\n 1.6s — 1.75× range from calm to busy, capped so the\n network never feels frantic. */}\n {layout === 'ring' && (() => {\n const busy = workingCount === 0 ? 0\n : workingCount <= 2 ? 1\n : workingCount <= 5 ? 2\n : 3;\n const spokeDur = [2.8, 2.4, 2.0, 1.6][busy];\n return onlineNodes.map((session, idx) => {\n const pos = nodePositions[session.alias];\n if (!pos) return null;\n const path = curvePath({ x: cx, y: cy }, pos, 0);\n const isActiveSpoke = activeAliases.has(session.alias);\n\n /* Round 241 / Loop: hub-link spokes (agent→hub paths in\n ring layout) eased state-flip between idle and active.\n Pre-R241 when a node sent or received a message its\n hub-spoke jumped one-frame from idle gray (pal.spoke-\n Stroke.idle + strokeWidth=1 + opacity=0.45) to active\n cyan (pal.spokeStroke.active + strokeWidth=2 + opacity\n =0.7) — three discrete property snaps in lockstep.\n R241 adds a 250ms ease-out transition list covering\n stroke + stroke-width + opacity so the activation\n 'lights up' smoothly. strokeDasharray stays binary\n (none ↔ '6 14') — dasharray doesn't interpolate\n cleanly between continuous and discrete forms across\n browsers (same lesson R167 documented for the node\n status ring). The CSS keyframe animation on idle\n spokes (anet-topo-spoke-flow) drives stroke-dashoffset\n separately and stays untouched. data-topo-hub-spoke-\n active surfaces the activity state for test probes\n (active spokes don't carry the bucket/dur attrs so\n they need their own data anchor). */\n return (\n <path\n key={`hub-${session.alias}`}\n d={path}\n fill=\"none\"\n stroke={isActiveSpoke ? pal.spokeStroke.active : pal.spokeStroke.idle}\n strokeWidth={isActiveSpoke ? 2 : 1}\n strokeDasharray={isActiveSpoke ? 'none' : '6 14'}\n opacity={isActiveSpoke ? 0.7 : 0.45}\n className={isActiveSpoke ? undefined : 'anet-topo-spoke-flow'}\n data-topo-spoke-bucket={isActiveSpoke ? undefined : busy}\n data-topo-spoke-dur={isActiveSpoke ? undefined : spokeDur}\n data-topo-hub-spoke-active={isActiveSpoke ? 'true' : 'false'}\n style={{\n transition: 'stroke 250ms ease-out, stroke-width 250ms ease-out, opacity 250ms ease-out',\n ...(isActiveSpoke ? {} : {\n animationDelay: `${-(idx * 0.25)}s`,\n // CSS var consumed by `.anet-topo-spoke-flow`\n // (line 859 of globals.css). React's CSSProperties\n // type doesn't model custom properties → cast\n // through Record<string, string>.\n ...({ ['--spoke-dur']: `${spokeDur}s` } as Record<string, string>),\n }),\n } as React.CSSProperties}\n />\n );\n });\n })()}\n\n {/* #111: prefix-group boundary boxes (Vincent 4722). Grid layout\n only — groupBoxes is empty in ring mode. Rendered behind the\n flow links + nodes; pointer-events off so they never intercept\n a node click. Restrained dashed container + group-name label. */}\n {groupBoxes.map((box, boxIdx) => {\n const isHovered = activeGroup === box.key;\n // R68: distinguish \"locked by click\" from \"currently hovered\".\n // R63 made pinned and hovered identical (both hit isHovered\n // via activeGroup). A user with one team pinned should see at\n // a glance which is the locked one even while their cursor\n // sweeps elsewhere. isPinned reads pinnedGroup directly\n // (NOT activeGroup) so the visual is specific to the sticky\n // state — transient hover keeps the R63 isHovered styling.\n const isPinned = pinnedGroup === box.key;\n // Round 18 / Loop: group-box hover linkage. The Round 8 fade\n // already dropped OUT-of-focus groups to 0.28, but the IN-focus\n // group sat at its baseline appearance — no positive emphasis.\n // Hovering now upgrades the box to an \"accent\" treatment:\n // solid stroke (not dashed), thicker, accent-coloured; brighter\n // text and slightly stronger fill. Label and box read as one\n // selected unit. Geometry unchanged → overlap test untouched.\n // R132: per-group marching-ants duration computed once,\n // reused on the data attribute + the inline custom property.\n const w = box.statuses.working;\n const marchDur = w >= 6 ? 8 : w >= 4 ? 10 : w >= 2 ? 12 : 14;\n return (\n <g\n key={`grp-${box.key}`}\n data-group={box.key}\n // Round 173 / Loop: group boxes pick up the first-paint\n // fade-in wave alongside R9 staggered nodes (0-540ms)\n // and R172 staggered edges (280-980ms). Pre-R173 the\n // structural box frames appeared instantly while the\n // nodes inside eased in — the reveal felt like\n // 'frame slams down, nodes drift in'. The .anet-fade-in\n // CSS animation (R3 origin, 0.15s ease-out, mount-once)\n // plays alongside the node R9 stagger; each box offset\n // by boxIdx × 60ms (cap at 8 indices so a fleet with\n // many groups still finishes within ~500ms) so boxes\n // appear like a soft sweep across the grid rather than\n // a single pop. animation-fill-mode default 'none' →\n // post-animation control reverts to the inline opacity\n // style below (1 / 0.28 based on filter pin state).\n // data-group-fade-delay exposes the computed delay for\n // test probes.\n className=\"transition-opacity anet-fade-in\"\n data-group-fade-delay={Math.min(boxIdx, 8) * 60}\n // R63: drop the blanket pointerEvents:'none' that\n // previously sat here. Chrome's SVG impl doesn't let a\n // child override a parent's `none` even though the spec\n // says it should — moving the property onto just the\n // rect (where it's needed so nodes underneath stay\n // clickable) lets the label text receive its own click.\n style={{\n opacity: !activeGroup || isHovered ? 1 : 0.28,\n animationDelay: `${Math.min(boxIdx, 8) * 60}ms`,\n }}\n >\n <rect\n x={box.x}\n y={box.y}\n width={box.w}\n height={box.h}\n rx=\"14\"\n fill={isLight ? '#0f172a' : '#a5b4fc'}\n // R68: 3-tier opacity + stroke ladder.\n // pinned → fill 0.08 / 0.13, stroke 3 px (locked)\n // hovered → fill 0.05 / 0.09, stroke 2 px (inspecting)\n // idle → fill 0.025 / 0.045, stroke 1.5 px dashed\n fillOpacity={isPinned ? (isLight ? 0.08 : 0.13)\n : isHovered ? (isLight ? 0.05 : 0.09)\n : (isLight ? 0.025 : 0.045)}\n stroke={(isPinned || isHovered) ? pal.legendAccent : pal.ringStroke}\n strokeWidth={isPinned ? 3 : isHovered ? 2 : 1.5}\n strokeDasharray={(isPinned || isHovered) ? 'none' : '6 6'}\n data-group-box-pinned={isPinned ? 'true' : 'false'}\n // R85: ambient \"marching ants\" drift on the perimeter\n // when this group has at least one working member, and\n // neither pin nor hover is active (those treatments\n // already shout for attention via solid stroke). 12s\n // cycle reads as ambient — the eye parses \"live work\n // here\" without registering the box as animating.\n // R132: per-group ant rate buckets on box.statuses.working\n // — the same coupling-to-busyness idiom R84 uses for the\n // hub and R131 uses for the outer-ring orbit, applied at\n // the GROUP scale. A team with one working member ambles;\n // a team with five working members visibly accelerates.\n // Bucket boundaries (1 / 2-3 / 4-5 / 6+) chosen to land\n // on the same 14/12/10/8 cadence ladder so the three\n // motion layers (hub / ring / group) keep a coherent\n // tempo grammar. Default 12s when working=0 doesn't\n // matter — the className is only applied when working>0.\n data-group-box-live={!isPinned && !isHovered && box.statuses.working > 0 ? 'true' : 'false'}\n data-group-box-march-dur={marchDur}\n data-group-box-lifted={(isPinned || isHovered) ? 'true' : 'false'}\n className={!isPinned && !isHovered && box.statuses.working > 0 ? 'anet-topo-groupbox-live' : undefined}\n // R142: drop-shadow filter when pinned or hovered. Box\n // visually \"rises off the canvas\" — same vocabulary\n // R18 KPI cards + R135 overlay panels use. Idle group\n // boxes carry no filter (purely flat dashed outline)\n // so the unstyled canvas stays uncluttered. Filter\n // affects paint area only, not the geometric bbox\n // the overlap-test reads, so zero-overlap invariant\n // is preserved.\n filter={(isPinned || isHovered) ? 'url(#topo-groupbox-lift)' : undefined}\n style={{\n /* Round 248 / Loop: append fill 200ms ease-out to\n the existing R66 transition list. Pre-R248 the\n rect's fill (isLight ? '#0f172a' (slate-900) :\n '#a5b4fc' (indigo-300)) snapped on theme toggle\n while stroke / fill-opacity / filter all eased.\n Closes the last theme-toggle snap on the group\n box surface — same idiom R246 + R247 used at\n per-node label-card and side-panel scopes. */\n transition: 'stroke 200ms ease-out, stroke-width 200ms ease-out, fill-opacity 200ms ease-out, filter 200ms ease-out, fill 200ms ease-out',\n pointerEvents: 'none',\n // CSS var consumed by `.anet-topo-groupbox-live`\n // (line 877 of globals.css). React's CSSProperties\n // type doesn't model custom properties, so cast\n // through Record<string, string>.\n ...({['--march-dur']: `${marchDur}s`} as Record<string, string>),\n }}\n />\n {/* R63: wrap label in a clickable <g> with an invisible\n rect hitbox. The text alone wasn't getting hit-tested\n reliably — the SVG-wide topo-panel <rect> intercepts\n at the label's screen position when the label sits at\n a high viewBox-y (it lands below where the compositor\n expects the text to paint on top, same gotcha as the\n recent-signal panel rows in R56). Hitbox rect + the\n parent <g> taking the click + onPointerDown stop-\n propagation match the R55/R56/R61 pattern. */}\n <g\n role=\"button\"\n tabIndex={0}\n aria-pressed={pinnedGroup === box.key}\n data-group-label-hit={box.key}\n className=\"anet-topo-svg-focus\"\n style={{ pointerEvents: 'all', cursor: 'pointer' }}\n onPointerDown={(e) => e.stopPropagation()}\n onClick={() => setPinnedGroup(prev => prev === box.key ? null : box.key)}\n // R86: hover the label → transient group focus. Releases\n // on leave; activeGroup = hoveredGroup ?? pinnedGroup\n // formula already handles transient-over-pin so a\n // user can spot-compare teams without losing their\n // pinned one. Closes the same R83-style hover/click\n // gap (segments) — now group labels carry it too.\n onPointerEnter={() => setHoveredGroupLabel(box.key)}\n onPointerLeave={() => setHoveredGroupLabel(prev => prev === box.key ? null : prev)}\n // R152: a11y completeness — R63 added role + tabIndex +\n // aria-pressed but never wired onKeyDown, so the focused\n // group label was tab-reachable but Enter/Space was a\n // no-op. Closes the last keyboard gap among the\n // role=\"button\" surfaces. Other group-pin trigger paths\n // (R69 palette, R74 cmdk, R86 hover, dispatchEvent) are\n // unchanged. Matches the onKeyDown idiom from R116 /\n // R139 / R140 / R151 (Enter & Space → same setter as\n // onClick, preventDefault on Space to stop SVG scroll).\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setPinnedGroup(prev => prev === box.key ? null : box.key);\n }\n }}\n >\n {/* R99: SVG <title> tooltip listing group members +\n status breakdown. Same info-density spirit as\n R97 pill tooltips + R98 node tooltips — anywhere\n a UI element says \"alpha · 3\" should hover-\n explain WHICH 3. Truncates at 8 aliases with a\n \"+N more\" suffix so a 20-member band doesn't\n paint a 22-line tooltip. */}\n {(() => {\n const members = Object.entries(groupKeys)\n .filter(([, key]) => key === box.key)\n .map(([alias]) => alias);\n const memberPreview = members.slice(0, 8).join(', ');\n const suffix = members.length > 8 ? ` + ${members.length - 8} more` : '';\n const statusSummary = [\n box.statuses.working > 0 ? `${box.statuses.working} working` : null,\n box.statuses.idle > 0 ? `${box.statuses.idle} idle` : null,\n box.statuses.offline > 0 ? `${box.statuses.offline} offline` : null,\n ].filter(Boolean).join(' · ');\n return (\n <title>{[\n `${box.key} (${members.length} member${members.length === 1 ? '' : 's'})`,\n statusSummary || null,\n `${memberPreview}${suffix}`,\n pinnedGroup === box.key ? 'click to release pin' : 'click to pin this group',\n ].filter(Boolean).join('\\n')}</title>\n );\n })()}\n <rect\n x={box.x + 6}\n y={box.y + 2}\n width={Math.min(box.w - 12, 240)}\n height={20}\n rx=\"4\"\n /* R107 / Loop: list-item tint extends to the SVG\n group labels — same idiom R104 added to recent-\n signal rows and R105 to the legend rows. The\n tint colour is pal.legendAccent (cyan) since\n groups don't carry an inherent swatch the way\n legend rows do; this matches R68's group-box\n isPinned/isHovered accent stroke for consistency.\n hover < pinned opacity so locked vs preview is\n discriminable at a glance. */\n fill={pinnedGroup === box.key || hoveredGroupLabel === box.key ? pal.legendAccent : 'transparent'}\n opacity={pinnedGroup === box.key ? (isLight ? 0.16 : 0.20)\n : hoveredGroupLabel === box.key ? (isLight ? 0.09 : 0.13)\n : 1}\n data-group-label-tinted={pinnedGroup === box.key ? 'pinned' : hoveredGroupLabel === box.key ? 'hover' : 'none'}\n style={{ transition: 'fill 150ms ease-out, opacity 150ms ease-out' }}\n />\n {/* Round 218 / Loop: group label gains a letter-spacing\n transition on pin — the text subtly spaces out\n (0px → 0.5px) when the group is locked, giving the\n pinned state its own typographic signature distinct\n from R63's transient hover fill brighten. Hover and\n pin share the same fill colour (legendHeadline), so\n pre-R218 the only thing distinguishing them was\n R142 drop-shadow + R68 rect stroke. R218 adds a\n type-level signal: pinned text spreads slightly,\n feels \"locked in\" / \"open and held\". Letter-\n spacing is one of the few SVG text properties that\n interpolates smoothly across the major browsers.\n Hover stays at default tracking — the spread is\n pin-exclusive so users can read pinned vs\n hovered at the text alone. transition 200ms\n matches R142 fill timing so all the group-label\n state-flip channels (fill colour, rect stroke,\n rect drop-shadow, label tracking) ease as one. */}\n <text\n x={box.x + 12}\n y={box.y + 14}\n fill={isHovered ? pal.legendHeadline : pal.legendText}\n fontSize=\"13\"\n fontFamily=\"monospace\"\n fontWeight=\"700\"\n style={{\n transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out',\n letterSpacing: isPinned ? '0.5px' : '0px',\n }}\n data-group-label={box.key}\n data-group-label-pinned={isPinned ? 'true' : 'false'}\n >\n {box.key}\n {/* Round 19 / Loop: member-count chip. Inline tspan stays\n in the single <text> bbox the overlap test reads, so\n the node↔label guard still catches if the chip ever\n pushes the label far enough right to clip a node.\n Smaller + lighter weight reads as metadata, not name. */}\n {/* Round 229 / Loop: member-count chip drops its explicit\n fill so it inherits from the parent <text>, which means\n R142's hover-fill transition (legendText → legend-\n Headline, 200ms ease-out) NOW carries the count chip\n with it. Pre-R229 the parent name brightened on\n hover while the count tspan stayed at legendText —\n \"name lit, count dimmer than at rest\" inverted the\n tonal hierarchy. Inheriting matches the name's\n transition; both rest and hover keep the SAME\n tonal relationship between name and count.\n 7th surface in the hover-deepen-own-hue family\n (legend rows, chip-row counts, status pip, recent\n row text, pressure-bar segments, group-box fill +\n this round's group-label-count chip).\n\n Also picks up tabular-nums (5th surface in the\n info-density tabular-nums sweep after R224 edge\n badge / R225 hub digit / R225 panel header /\n R225 recent row count). The member count rolls\n over often (4→5→…→9→10 as a group grows) and\n lives at a fixed dx=6 offset from the name, so a\n digit-width jitter at 9→10 used to shift the\n whole count visibly. Tabular locks it. */}\n <tspan\n dx=\"6\"\n fontSize=\"11\"\n fontWeight=\"400\"\n data-group-label-count={box.key}\n data-group-label-count-value={box.count}\n style={{ fontVariantNumeric: 'tabular-nums' }}\n >· {box.count}</tspan>\n {/* Round 58 / Loop: status mix pip strip. Compact text-\n based chips (e.g. \"2w 1i\") so the strip stays inside\n the same <text> bbox the overlap-test reads — keeps\n the R27 label↔label and R19 node↔label guards intact.\n Each tier is colour-coded against the legend swatches\n and only renders when count > 0, so a healthy all-\n working group reads simply \" · 2w\".\n\n Round 207 / Loop: each tspan eases in on mount\n via anet-fade-in. Pre-R207 when a group's first\n working node went idle (or first idle node went\n working), the new tier's tspan snap-popped into\n the label. Same snap-on-mount issue R203 fixed\n for recent-signal rows, applied at the group-\n label scope. Each tier is keyed on its boolean\n mount, so the animation fires once when the\n tspan first appears (count crosses 0 → 1+),\n not on every count update (e.g., 1 → 2 working\n preserves the tspan via React reconciliation).\n Exit remains snap — matches R190's \"fade-IN\n smooth, accept exit snap\" trade-off used for\n the R129 hot-tail. */}\n {/* Round 230 / Loop: tabular-nums on the 3 status pips\n so the count digit doesn't jitter the adjacent\n pip when a tier crosses 9 → 10. The pips render\n in sequence at dx=8/4/4 — width-shift on any\n tier propagates rightward through the strip,\n visibly compressing or stretching the gap\n between adjacent tier chips. Tabular locks the\n digit so the strip stays stable as tiers grow.\n 6th surface in the info-density tabular-nums\n sweep after R224 edge badge / R225 hub digit /\n R225 panel header / R225 recent row count /\n R229 group-label count. Tier-specific fill\n colours stay (semantic — working green /\n idle teal / offline slate). */}\n {/* Round 253 / Loop: append fill 200ms ease-out to\n each tspan's style so theme toggle eases the\n tier-coloured pips alongside every other\n theme-driven element. R230's tabular-nums\n stays. */}\n {/* Round 319 / Loop: drop a tier pip when its count\n equals box.count — i.e. single-tier groups (all\n working, all idle, all offline). Pre-R319 a 4-all-\n idle group rendered as `P站 · 4 4i` with the \"4\"\n visually doubled; Vincent telegram 5304 flagged\n this as 比较难看 in a real-data screenshot\n (ai-insight · 6 6i, blueleap · 3 3i, P站 · 4 4i).\n The dropped pip's information is already conveyed\n by the group-box stroke colour (R68 isPinned/\n hover accent uses the dominant-tier hue) plus\n the SVG <title> tooltip listing the status\n breakdown. Multi-tier groups (e.g. `alpha · 3\n 2w 1i`) render unchanged — those pips genuinely\n add breakdown info that the total doesn't carry. */}\n {box.statuses.working > 0 && box.statuses.working !== box.count && (\n <tspan\n dx=\"8\"\n fill={isLight ? '#059669' : '#22c55e'}\n fontSize=\"11\"\n fontWeight=\"600\"\n className=\"anet-fade-in\"\n data-group-pip=\"working\"\n style={{ fontVariantNumeric: 'tabular-nums', transition: 'fill 200ms ease-out' }}\n >{box.statuses.working}w</tspan>\n )}\n {box.statuses.idle > 0 && box.statuses.idle !== box.count && (\n <tspan\n dx=\"4\"\n fill={isLight ? '#0d9488' : '#2dd4bf'}\n fontSize=\"11\"\n fontWeight=\"600\"\n className=\"anet-fade-in\"\n data-group-pip=\"idle\"\n style={{ fontVariantNumeric: 'tabular-nums', transition: 'fill 200ms ease-out' }}\n >{box.statuses.idle}i</tspan>\n )}\n {box.statuses.offline > 0 && box.statuses.offline !== box.count && (\n <tspan\n dx=\"4\"\n fill={isLight ? '#94a3b8' : '#6b7280'}\n fontSize=\"11\"\n fontWeight=\"600\"\n className=\"anet-fade-in\"\n data-group-pip=\"offline\"\n style={{ fontVariantNumeric: 'tabular-nums', transition: 'fill 200ms ease-out' }}\n >{box.statuses.offline}o</tspan>\n )}\n </text>\n </g>\n </g>\n );\n })}\n\n {/* directed message flows */}\n {flowLinks.map((link, index) => {\n const from = nodePositions[link.from];\n const to = nodePositions[link.to];\n if (!from || !to) return null;\n\n // Round 7 / Loop: lift now scales with distance so short links\n // aren't over-bent (long links keep the ~36px hump), and the\n // particle period shortens with link.count so busier edges\n // visibly pulse faster — instant info density on top of the\n // existing stroke-width-by-count chip.\n const dist = Math.hypot(to.x - from.x, to.y - from.y);\n const lift = (index % 2 === 0 ? 1 : -1) * Math.min(36, dist * 0.18);\n const path = curvePath(from, to, lift);\n const width = Math.min(2 + link.count, 7);\n const duration = Math.max(0.9, 2.6 / Math.sqrt(link.count));\n // Round 231 / Loop: per-edge phase stagger lifted into a\n // named constant so the R75 arrival ping + R76 dispatch\n // pulse SMIL animates can RE-COUPLE to the R103 particle's\n // cycle. Pre-R103 (when particle started at phase 0) the\n // ping fired at \"near end of cycle\" (-0.92*dur) and dispatch\n // at \"cycle start\" (0) — both phase-coincident with particle\n // arrival/departure respectively. R103's golden-ratio\n // stagger broke that coupling — particle now started at\n // phase (index*0.37)%dur while ping+pulse stayed at fixed\n // offsets, so the rings fired at random moments relative\n // to particle position. R231 expresses dispatch_begin and\n // arrival_begin in terms of THIS stagger so they fire\n // exactly when the particle is at source / near destination\n // respectively — restoring R75/R76's original semantic\n // and unifying the three SMIL elements into one\n // synchronised per-edge animation set.\n const stagger = (index * 0.37) % duration;\n // Round 10 / Loop: freshness fade. An edge that fired ≤30s ago\n // stays at full intensity; over 5 minutes it decays to ~35%.\n // Surfaces \"what's happening now\" vs background chatter without\n // hiding old flow entirely (some context still useful). `now`\n // captured at useMemo-recompute time (every 5s message refresh)\n // — accuracy is within the poll interval, plenty.\n const ageMs = link.last_at ? Math.max(0, Date.now() - Date.parse(link.last_at)) : 0;\n const fresh = Math.max(0.35, 1 - ageMs / (5 * 60 * 1000));\n // Round 16 arrow-tier binning — keep `topo-arrow` as the\n // medium tier id so the legend swatch picks it up unchanged.\n const arrowId = link.count <= 2 ? 'topo-arrow-s'\n : link.count <= 4 ? 'topo-arrow'\n : 'topo-arrow-l';\n\n // Round 39 / Loop: edge hover tooltip — surface the same\n // last_at + count info the freshness fade and arrow tier\n // already encode visually, in plain text. The stroke is the\n // hover target; SVG `<title>` honours newlines on every\n // browser the dashboard targets.\n const lastAt = relativeAgo(link.last_at);\n const tooltip = `${link.from} → ${link.to}\\n${link.count} message${link.count === 1 ? '' : 's'}${lastAt ? ` · last ${lastAt}` : ''}`;\n // Round 40 / Loop: edges follow node hover — when an alias is\n // hovered, every edge touching it brightens, the rest fade.\n // Pairs with the Round 8 group-focus fade on nodes: hover a\n // node to find \"who is this agent talking to\" at a glance.\n // No hover → multiplier is 1.0 (current behaviour preserved).\n // Round 50 / Loop: edge-on-self priority. When the user hovers\n // a flow edge directly (R48 widened the hitbox so this is now\n // a precise gesture), THAT edge gets the strongest boost (2.0)\n // and every other edge dims to 0.35. Node-hover (R40) keeps\n // its own ladder. Edge-hover and node-hover are mutually\n // exclusive in practice — the cursor is over one or the\n // other — but the order below makes edge-hover win if both\n // ever read truthy at the same React tick.\n // Round 53 / Loop: in-group edges follow team focus. R40\n // brightened only edges touching the exact hovered alias —\n // but with R8/R18 prefix-clustering, a user hovering one\n // member is asking about the team. So when BOTH endpoints of\n // an edge share the hoveredGroup (and neither is the exact\n // hovered alias — that gets the stronger 1.7×), the edge\n // boosts to 1.3×. Edges leaving the team (one endpoint in,\n // one out) still dim to 0.35× per R40 since they read as\n // background to the focus. Singletons fall through unchanged\n // (their group key is the alias itself, so bothInHoveredGroup\n // is impossible for a non-self-edge).\n // R116: composes hover ?? pin — a pinned edge stays \"hot\" after the cursor leaves.\n const isHoveredEdge = activeEdgeKey === link.key;\n const fromGroup = groupKeys[link.from] ?? link.from;\n const toGroup = groupKeys[link.to] ?? link.to;\n const bothInHoveredGroup = !!activeGroup && fromGroup === activeGroup && toGroup === activeGroup;\n // R63: also gate \"no filter\" on activeGroup so pinnedGroup\n // alone activates the in-group 1.3× boost + non-group dim.\n // When neither a node nor a group is in focus, mul is 1.\n // R77: when the user hovers the \"N active links\" chip, the\n // baseline 1× becomes 1.5× — every flow brightens at once.\n // Sits inside the no-other-hover branch so it doesn't fight\n // the edge-hover (2.0×) or node-hover (1.7×) priorities.\n const edgeOpacityMul = isHoveredEdge\n ? 2.0\n : activeEdgeKey\n ? 0.35\n : !hoveredAlias && !activeGroup\n ? (hoveredActiveLinks ? 1.5 : 1)\n : (link.from === hoveredAlias || link.to === hoveredAlias)\n ? 1.7\n : bothInHoveredGroup\n ? 1.3\n : 0.35;\n // Round 50: the hovered edge also visibly thickens so the eye\n // tracks it even at low message counts (width was 3 → 4.5 on\n // hover). 1.4× is enough to read as \"lifted\" without\n // breaching the 16-px hitbox bound.\n const renderWidth = isHoveredEdge ? Math.min(width * 1.4, 10) : width;\n return (\n <g\n key={link.key}\n // Round 172 / Loop: edges fade-in alongside the R9\n // staggered node reveal so the canvas first-paint\n // reads as one coordinated wave instead of \"edges\n // pop, nodes ease in\". The .anet-fade-in CSS\n // animation (0.15s ease-out, runs once on mount,\n // R3 origin) plays only on first render — React\n // preserves the <g> via the link.key on subsequent\n // re-renders so the animation doesn't replay when\n // flowLinks recomputes (every 5s SSE poll).\n // Animation-delay offsets each edge by 280ms +\n // 35ms × index so edges start fading in AFTER\n // most nodes have appeared (node R9 stagger caps\n // at ~600ms; ring layout R72 emanates 0→540ms).\n // Cap at 20 indices so a busy fleet with 50\n // flowLinks still finishes within ~1s. Respects\n // prefers-reduced-motion via R29 globals.css\n // blanket that neutralises animation-duration.\n className=\"anet-fade-in\"\n style={{\n animationDelay: `${280 + Math.min(index, 20) * 35}ms`,\n }}\n data-edge-group={link.key}\n >\n {/* Round 48 / Loop: invisible hover hitbox — visible flow\n path is 3-7 px wide and damn hard to hover precisely.\n Stack a transparent 16-px-wide stroke behind it so the\n cursor only needs to be ~8 px from the line for the\n tooltip to fire. Native <title> moves here; the\n visible path no longer needs it. pointer-events on\n the visible path drop to \"none\" since the hitbox\n owns the hover surface. */}\n <path\n d={path}\n fill=\"none\"\n stroke=\"transparent\"\n strokeWidth={Math.max(width + 10, 16)}\n style={{ pointerEvents: 'stroke' }}\n data-edge-hitbox\n onMouseEnter={() => setHoveredEdgeKey(link.key)}\n onMouseLeave={() => setHoveredEdgeKey(prev => prev === link.key ? null : prev)}\n >\n <title>{tooltip}</title>\n </path>\n {/* Round 166 / Loop: stroke-width transition pairs\n with R164 edge badge r-lift. Pre-R166 the\n visible flow path's hover thickening (R50:\n renderWidth = isHoveredEdge ? width * 1.4 :\n width) snapped instantly even though opacity\n transitioned smoothly. Edge hover now lifts\n the line AND the badge in coordinated 300ms\n ease-out motion. Drop the Tailwind transition\n class for inline style so both opacity and\n stroke-width pick up the same timing without\n arbitrary-property class compilation risk.\n data-edge-visible exposes the path for test\n probes (the R48 hitbox sibling already has\n data-edge-hitbox). Respects prefers-reduced-\n motion via the R29 globals.css blanket\n override that neutralises transition-duration\n universally. */}\n {/* Round 245 / Loop: edge surface picks up stroke\n color transition for theme-toggle smoothing.\n R166 already eased opacity + stroke-width on the\n visible flow path; the stroke COLOR (pal.flowEdge:\n cyber cyan ↔ light emerald) and the underlying\n flow-rail's stroke (pal.flowPath: cyber pale-sky\n ↔ light slate-600) still snapped on theme switch.\n The rest of the topology smooths theme through R4\n transitions (status rings) / R242 chat-target ring\n / R244 halos / R241 hub spokes / R240 backdrop\n spokes — R245 closes the edge surface.\n\n Visible flow path: append 'stroke 300ms ease-out'\n to the existing transition list (300ms matches\n R166 opacity + stroke-width pace).\n\n Flow rail (dashed underline): convert the Tailwind\n `transition-opacity` className to inline style so\n we can list opacity AND stroke together at 300ms\n ease-out (same idiom R201 used on the working/\n online chips to splice in additional properties\n beside Tailwind's). data-edge-flow-rail attr\n surfaces the path for test introspection. */}\n <path\n d={path}\n fill=\"none\"\n stroke={pal.flowEdge}\n strokeWidth={renderWidth}\n opacity={Math.min(1, (isLight ? 0.22 : 0.28) * fresh * edgeOpacityMul)}\n filter={isLight ? undefined : 'url(#topo-glow)'}\n markerEnd={`url(#${arrowId})`}\n data-edge-visible={link.key}\n style={{\n pointerEvents: 'none',\n transition: 'opacity 300ms ease-out, stroke-width 300ms ease-out, stroke 300ms ease-out',\n }}\n />\n <path\n id={`flow-path-${index}`}\n d={path}\n fill=\"none\"\n stroke={pal.flowPath}\n strokeWidth=\"1\"\n strokeDasharray=\"2 12\"\n opacity={Math.min(1, (isLight ? 0.4 : 0.75) * fresh * edgeOpacityMul)}\n data-edge-flow-rail={link.key}\n style={{ transition: 'opacity 300ms ease-out, stroke 300ms ease-out' }}\n />\n {!reducedMotion && (\n /* Round 103 / Loop: phase-stagger the particles so\n concurrent edges don't pulse in lockstep. SMIL\n `begin` accepts negative offsets to shift the cycle\n backwards in time, which means the particle starts\n mid-flight on first paint — no visible \"all\n particles spawn from source simultaneously\" tell.\n `(index * 0.37) % duration` gives a deterministic,\n well-distributed offset (the golden-ratio-ish 0.37\n fraction prevents lining up when N is a small\n multiple). Edge order is stable (sorted by recent\n activity), so the offsets feel calm rather than\n reshuffling each refresh. */\n <circle\n r=\"4\"\n fill={pal.flowParticle}\n filter={isLight ? undefined : 'url(#topo-glow)'}\n opacity={Math.min(1, fresh * edgeOpacityMul)}\n data-edge-particle={link.key}\n /* Round 252 / Loop: particle picks up fill +\n opacity transition for theme-toggle smoothing.\n Pre-R252 pal.flowParticle (cyber #fef08a yellow\n ↔ light #f59e0b amber) snapped on theme toggle\n while every other edge element eased (R245\n paths, R251 badge, R242 chat-target, R233\n endpoint ring). opacity is freshness-driven so\n it transitions per-frame as fresh decays anyway\n — but adding opacity to the explicit transition\n list also covers theme toggle (R3 className\n transition-opacity duration-300 was previously\n absent on this circle). */\n style={{ transition: 'fill 200ms ease-out, opacity 200ms ease-out' }}\n >\n <animateMotion\n dur={`${duration}s`}\n begin={`-${stagger.toFixed(3)}s`}\n repeatCount=\"indefinite\"\n path={path}\n />\n </circle>\n )}\n {/* Round 75 / Loop: arrival ping at the destination. The\n particle currently fades into the arrow marker\n silently — adding a small radiating ring synchronised\n to the particle's period turns message delivery into\n a visible event. begin = -dur*0.92 offsets the\n animation so the ring expands NEAR the end of each\n cycle (≈when the particle arrives). Gated by\n reducedMotion and on fresh > 0.5 — stale edges that\n haven't fired in minutes don't need the eye-grab.\n data-arrival-ping for testability.\n\n Round 279 / Loop: arrival ping RETIRED (R75 + R228\n + R231 + R252 family) per 减法 cut #5. Per active\n edge the SMIL family was: particle (R50\n animateMotion) + arrival ping (R75 r+opacity SMIL)\n + dispatch pulse (R76 r+opacity SMIL). For a 5-\n edge fleet that's 5×3 = 15 simultaneous SMIL.\n The PARTICLE (a moving dot along the path) is the\n primary \"data flowing from A → B\" visual signal;\n ping + pulse are secondary \"arrival/dispatch\n confirmation\" that the moving particle already\n conveys. Cull ping + pulse, keep particle.\n `false &&` gates the render; code preserved for\n rollback. */}\n {false && !reducedMotion && fresh > 0.5 && (\n <circle\n cx={to.x}\n cy={to.y}\n r=\"0\"\n fill=\"none\"\n stroke={pal.flowEdge}\n strokeWidth=\"1.5\"\n opacity=\"0\"\n /* Round 252 / Loop: stroke transition for theme\n toggle. SMIL animates r + opacity continuously;\n stroke is static per render but theme-driven\n (pal.flowEdge: cyber cyan ↔ light emerald). */\n style={{ pointerEvents: 'none', transition: 'stroke 200ms ease-out' }}\n data-arrival-ping={link.key}\n >\n {/* Round 228 / Loop: pulse-pop ease curves on the\n arrival ping SMIL — extends R227's keySplines\n adoption from the click ripple (one-shot, two-\n value linear) to the canvas's repeating delivery-\n confirmation surfaces. Both <animate>s use\n calcMode=spline with keyTimes='0;0.5;1' (two\n segments).\n • r grows 0→14→22 (monotonic): unified ease-out\n across both segments — the ring decelerates\n as it expands, settling at its widest.\n • opacity bumps 0→0.55→0 (pulse): ease-out on\n the rise (fast appearance), ease-in on the\n fall (slow start of fade then accelerates) —\n the canonical \"pulse-pop\" kinetic shape.\n Together r decelerating and opacity pulse-popping\n give the arrival ping a real-event physicality\n instead of the prior linear-velocity tick. */}\n <animate\n attributeName=\"r\"\n values=\"0;14;22\"\n dur={`${duration}s`}\n begin={`-${((stagger + duration * 0.92) % duration).toFixed(3)}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.25 0.1 0.25 1;0.25 0.1 0.25 1\"\n />\n <animate\n attributeName=\"opacity\"\n values=\"0;0.55;0\"\n dur={`${duration}s`}\n begin={`-${((stagger + duration * 0.92) % duration).toFixed(3)}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.25 0.1 0.25 1;0.42 0 1 1\"\n />\n </circle>\n )}\n {/* Round 76 / Loop: source dispatch pulse — mirror to the\n R75 arrival ping. begin = 0s (start of cycle) so the\n ring expands as the particle LEAVES the source. Only\n fires for high-traffic edges (link.count >= 3) — on\n quiet conversations the canvas should stay calm; on\n busy senders the pulse plus arrival ping bookend\n every message in flight, making the topology feel\n alive. Same fresh/reducedMotion gates as R75. Slightly\n smaller radius (0→12→18 vs R75's 0→14→22) so the\n source reads as \"smaller event than arrival\" — the\n destination is the meaningful endpoint.\n\n Round 279 / Loop: dispatch pulse RETIRED with the\n arrival ping (R75) above — same 减法 rationale.\n Particle remains as the sole \"data flow\" SMIL\n signal per active edge. */}\n {false && !reducedMotion && fresh > 0.5 && link.count >= 3 && (\n <circle\n cx={from.x}\n cy={from.y}\n r=\"0\"\n fill=\"none\"\n stroke={pal.flowEdge}\n strokeWidth=\"1.5\"\n opacity=\"0\"\n /* Round 252 / Loop: stroke transition for theme\n toggle. Same idiom as arrival ping above. */\n style={{ pointerEvents: 'none', transition: 'stroke 200ms ease-out' }}\n data-dispatch-pulse={link.key}\n >\n {/* Round 228 / Loop: same pulse-pop curves as the\n arrival ping above. r ease-out + opacity\n ease-out→ease-in. The dispatch pulse is smaller\n (0→12→18 vs arrival's 0→14→22), but the kinetic\n feel should be identical — both bookend a\n single message in flight, so they should\n physically ease the same way. */}\n <animate\n attributeName=\"r\"\n values=\"0;12;18\"\n dur={`${duration}s`}\n begin={`-${stagger.toFixed(3)}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.25 0.1 0.25 1;0.25 0.1 0.25 1\"\n />\n <animate\n attributeName=\"opacity\"\n values=\"0;0.45;0\"\n dur={`${duration}s`}\n begin={`-${stagger.toFixed(3)}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.25 0.1 0.25 1;0.42 0 1 1\"\n />\n </circle>\n )}\n {/* Round 100 / Loop: midpoint count badge for high-\n traffic edges. Width already gauges intensity but\n \"5 vs 12\" is hard to read off stroke alone. Render\n a compact pill at the bezier midpoint (t=0.5 →\n midpoint + perpendicular × lift/2) showing the\n integer count. Threshold link.count >= 3 keeps\n the canvas quiet for sparse traffic. Bezier\n midpoint math: for a quadratic curve M A Q C B,\n the t=0.5 point is (A + 2C + B) / 4, which\n simplifies to (midAB) + 0.5 × (control - midAB)\n = midAB + perpendicular × lift/2. */}\n {(() => {\n // Round 215 / Loop: badge always-mounts; visibility\n // crossfades via the wrapper <g>'s opacity instead of\n // React conditional mount/unmount on link.count >= 3.\n // Pre-R215 a flow's first 2 → 3 message crossing made\n // the badge appear in one frame, and a hot flow\n // tapering 3 → 2 vanished in one frame. R215 extends\n // the always-mount-opacity-gate idiom (R181 / R182 /\n // R183 ring family + R213 hub digit/highlight + R214\n // pulse dot) to the canvas-edge surface. Inner R164\n // r-lift / R188 stroke transitions continue to ease\n // their respective state flips independently. pointer\n // events gated to 'none' when invisible so a sub-\n // threshold badge can't intercept the hitbox click.\n const visible = link.count >= 3;\n const midX = (from.x + to.x) / 2;\n const midY = (from.y + to.y) / 2;\n const dx = to.x - from.x;\n const dy = to.y - from.y;\n const len = Math.hypot(dx, dy) || 1;\n const badgeX = midX + (-dy / len) * lift * 0.5;\n const badgeY = midY + ( dx / len) * lift * 0.5;\n const badgeOpacity = visible ? Math.min(1, fresh * edgeOpacityMul) : 0;\n /* R121: the badge becomes a canvas-side click-to-pin\n affordance. R100 introduced it as a passive count\n display; R116 added pinnedEdgeKey. Joining them\n lets users pin a flow directly from the canvas\n without crossing to the recent-signal panel.\n pointerEvents move from 'none' to 'all' on the\n wrapper <g>; the underlying R48 edge hitbox is\n wider (16 px) than the 18-px badge diameter so\n clicks landing on either still route correctly\n — the badge consumes its small footprint, the\n hitbox owns the rest. stopPropagation on\n pointerdown so the SVG pan capture doesn't\n redirect the click. */\n const isPinned = pinnedEdgeKey === link.key;\n // R126: hot-edge accent. The edge stroke width (line\n // 2344) already scales with count but clamps at 7 —\n // so count=5 and count=50 look identical at the line,\n // and the only signal differentiating them is the\n // integer in the badge. Bucket count into a \"hot\"\n // band (≥ 10) and flip the badge stroke to a warmer\n // tone + thicker ring so busy lanes telegraph at a\n // glance without reading the digit. Reuses the amber\n // family from R125 (chip empty-state) — semantic is\n // \"draw the eye here\"; R125 amber is in the chip row\n // above the SVG, R126 amber is on the canvas, so the\n // surfaces never compete in the same eye-sweep. Pin\n // still wins (uses legendHeadline) so a pinned hot\n // edge reads as \"locked + busy\" via the badge text\n // and edge brightness, not via a third stroke colour.\n const isHot = link.count >= 10;\n const hotStroke = isLight ? '#d97706' : '#fbbf24';\n return (\n <g\n data-edge-count-badge={link.key}\n data-edge-count-badge-pinned={isPinned ? 'true' : 'false'}\n data-edge-count-badge-hot={isHot ? 'true' : 'false'}\n data-edge-count-badge-visible={visible ? 'true' : 'false'}\n role={visible ? 'button' : undefined}\n tabIndex={visible ? 0 : -1}\n aria-pressed={visible ? isPinned : undefined}\n aria-hidden={visible ? undefined : true}\n className=\"anet-topo-svg-focus\"\n style={{\n pointerEvents: visible ? 'all' : 'none',\n cursor: visible ? 'pointer' : undefined,\n transition: 'opacity 300ms ease-out',\n }}\n opacity={badgeOpacity}\n onPointerDown={(e) => e.stopPropagation()}\n // R122: badge hover propagates to hoveredEdgeKey so\n // moving the cursor onto the badge lights the\n // same endpoint rings + edge brighten as hovering\n // the line. R121 only wired click; the badge sat\n // visually separate from the line on hover,\n // which felt like two surfaces rather than one.\n onMouseEnter={() => setHoveredEdgeKey(link.key)}\n onMouseLeave={() => setHoveredEdgeKey(prev => prev === link.key ? null : prev)}\n // Round 185 / Loop: edge badge click fires the same\n // one-shot expanding-ring ripple R14 uses for node\n // click and R52 uses for hub click — anchored at\n // the badge midpoint with the edge's own flowEdge\n // colour. Closes the click-feel idiom across all\n // three pinnable canvas surfaces (hub / node /\n // edge badge). Reused setClickRipple state machine\n // so only one ripple at a time; ts coordinate\n // guard in the setTimeout cleanup prevents an\n // older ripple from clobbering a newer one if\n // the user clicks two badges in quick succession.\n onClick={(e) => {\n e.stopPropagation();\n setPinnedEdgeKey(prev => prev === link.key ? null : link.key);\n const ts = Date.now();\n setClickRipple({ ts, x: badgeX, y: badgeY, r0: 10.5, color: pal.flowEdge });\n setTimeout(() => setClickRipple(prev => prev && prev.ts === ts ? null : prev), 600);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setPinnedEdgeKey(prev => prev === link.key ? null : link.key);\n const ts = Date.now();\n setClickRipple({ ts, x: badgeX, y: badgeY, r0: 10.5, color: pal.flowEdge });\n setTimeout(() => setClickRipple(prev => prev && prev.ts === ts ? null : prev), 600);\n }\n }}\n >\n <title>{isPinned\n ? `${link.from} → ${link.to} (${link.count}) — click to release pin`\n : `${link.from} → ${link.to} (${link.count}) — click to pin`}</title>\n {/* Round 164 / Loop: edge badge gains hover-lift\n to match the 5-surface hover-elevation\n family (R51 node / R135 panel / R142 group\n box / R143 recent row / R144 legend row).\n Pre-R164 the badge had R122 hover→edge-brighten\n propagation but the badge ITSELF stayed\n static, so the cursor-on-target feedback\n felt mismatched with every other interactive\n surface. Bumping r 9 → 10.5 on hover OR pin\n gives the same \"lift\" gesture in canvas\n space (the badge sits on a curved path, so\n translate-Y wouldn't track the line; radius\n growth is the SVG-native equivalent). Pin\n and hover share the lift so a pinned badge\n stays visually raised even after mouseleave —\n mirrors R143/R144 where row pin gets the\n same lift as row hover. Pinned still keeps\n its R121 stroke change (legendHeadline +\n width 2) so pin and hover stay\n discriminable on the same lifted state.\n strokeWidth stays at 1 / 2 — won't trip the\n R51 overlap-test sentinels (1.5 / 3 are\n reserved). transition keeps the lift smooth\n (180ms ease-out) and respects prefers-\n reduced-motion via the globals.css blanket\n override that neutralises transitions.\n\n Six surfaces now share the hover-elevation\n idiom: nodes (R51), panels (R135), group\n boxes (R142), recent rows (R143), legend\n rows (R144), edge badges (R164). */}\n {/* Round 188 / Loop: extend the badge transition to\n include stroke + stroke-width. R164 added the\n r 9↔10.5 lift; R188 closes the smoothness gap\n on the R121 pin-stroke flip (cyan flowEdge ↔\n legendHeadline) and R126 hot-lane flip (cyan\n ↔ amber). Both used to snap when crossing the\n state boundary (pin click, or count crossing\n 10). Now they ease 300ms through the colour\n and width change — same idiom R167 uses for\n the node status ring. The badge strokeWidth\n values are 1/2 (not R51 sentinels 1.5/3) so\n the always-rendered badge stays invisible to\n the overlap-test guard rails. */}\n {/* Round 251 / Loop: edge badge circle transition\n list grows fill + opacity at 200ms so theme\n toggle no longer snaps the badge background\n while the rest of the circle eases.\n Pre-R251:\n r 180ms (R164 hover lift)\n stroke 300ms (R188 hot/pinned colour flip)\n stroke-width 300ms (R188 hot/pinned width flip)\n fill (pal.legendBox.fill: cyber #020617 ↔ light\n #ffffff) and opacity (cyber 0.82 ↔ light 0.95)\n were theme-driven but missed from the list —\n the badge chrome snapped on theme switch while\n the per-edge ring + visible flow path (R245)\n and per-node surfaces (R246) all eased.\n R251 closes the per-edge surface theme-toggle\n smoothness — every theme-driven property on\n every edge element now eases under cyber↔light. */}\n <circle\n cx={badgeX} cy={badgeY}\n r={isHoveredEdge || isPinned ? 10.5 : 9}\n fill={pal.legendBox.fill}\n stroke={isPinned ? pal.legendHeadline : isHot ? hotStroke : pal.flowEdge}\n strokeWidth={isPinned ? 2 : isHot ? 2 : 1}\n opacity={isLight ? 0.95 : 0.82}\n data-edge-badge-lifted={(isHoveredEdge || isPinned) ? 'true' : 'false'}\n style={{ transition: 'r 180ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out, fill 200ms ease-out, opacity 200ms ease-out' }}\n />\n {/* Round 224 / Loop: edge badge text gains the 4th\n pin-signature typography. Pre-R224 the digit\n rendered with no transition surface: when the\n flow crossed count=10 (isHot flip) the badge\n stroke eased 300ms (R188) but the digit itself\n stayed dead-typographic. R224 adds two clean\n improvements stacked on the same <text> node:\n\n 1) fontVariantNumeric: 'tabular-nums' — locks\n digit width so a 9→10 transition doesn't\n jitter the textAnchor='middle' centering by\n half a glyph. The badge is on a curved\n flow path; any width-change of the centered\n digit visibly shifts the anchor relative\n to the underlying circle. Info-density\n win — digits transition cleanly without\n pixel-jitter at the boundary.\n\n 2) letterSpacing pin signature, 4th surface\n after R218 group label / R219 legend row /\n R220 recent row. Baseline 0px; widens to\n 0.4px when isPinned || isHot. The transition\n marks the \"this lane just went special\"\n event typographically — same 300ms cadence\n as the R188 stroke flip, so the badge stroke\n + text co-ease on the hot/pin threshold.\n '0px' resolves to keyword 'normal' in\n computed style (R218 test trap learned);\n test parsers must accept either form.\n\n data-edge-badge-text-pin attr surfaces the\n isPinned||isHot state for introspection. */}\n <text\n x={badgeX} y={badgeY + 3}\n textAnchor=\"middle\"\n fill={pal.legendHeadline}\n fontSize=\"10\"\n fontFamily=\"monospace\"\n fontWeight=\"700\"\n data-edge-badge-text={link.key}\n data-edge-badge-text-pin={(isPinned || isHot) ? 'true' : 'false'}\n style={{\n pointerEvents: 'none',\n fontVariantNumeric: 'tabular-nums',\n letterSpacing: (isPinned || isHot) ? '0.4px' : '0px',\n transition: 'letter-spacing 300ms ease-out',\n }}\n >{link.count}</text>\n </g>\n );\n })()}\n </g>\n );\n })}\n\n {/* center hub — round 39 sized + round 13 restraint.\n The hub is the control-plane anchor. r39 gave it two outward\n pulses (r 10→38, 2.4s, double-phase) which read as \"loudest\n node in the room\" — wrong semantics: anchors don't emit,\n they hold. r13 swaps the kinetic pulse for a slow opacity\n breath on the static halo (4s, ±15% from base), so the hub\n stays alive without throwing kinetic energy outward. The\n dual-circle \"lit lamp\" core is unchanged. */}\n {layout === 'ring' && (<g\n data-topo-hub\n data-topo-hub-hovered={hoveredHub ? 'true' : 'false'}\n // Round 159 / Loop: the hub is the most visually\n // prominent interactive element on the canvas (R39\n // enlarged it, R52 made it click to fitView, R115\n // added a hover ring, R43 gave it a tooltip) — but\n // R151-R157's a11y sweep skipped it. role/tabIndex/\n // aria-label/onKeyDown bring it to parity with node <g>\n // (R151), group label hit (R152), and minimap (R157).\n // anet-topo-svg-focus picks up R156's explicit cyan\n // focus ring (default browser SVG focus rect is hard\n // to see against the dark canvas).\n role=\"button\"\n tabIndex={0}\n aria-label={(() => {\n const parts = ['Network hub'];\n if (onlineNodes.length > 0) parts.push(`${onlineNodes.length} online`);\n if (workingCount > 0) parts.push(`${workingCount} working`);\n if (flowLinks.length > 0) parts.push(`${flowLinks.length} active link${flowLinks.length === 1 ? '' : 's'}`);\n return parts.join(' · ') + ' — Enter to fit view';\n })()}\n // Round 176 / Loop: hub joins the first-paint fade-in\n // family as the 6th surface. The hub is the visual anchor\n // — every other ring-layout reveal layer (R174 tier rings,\n // R9 nodes, R172 edges) emanates outward FROM it — yet\n // pre-R176 the hub itself popped in instantly while the\n // wave it should be leading staggered around it. Adding\n // .anet-fade-in at delay 0 (no animation-delay needed)\n // places the hub as the canvas-center anchor that the\n // tier wave grows from. Composes cleanly with the existing\n // anet-topo-svg-focus class (R159 keyboard focus ring).\n className=\"anet-topo-svg-focus anet-fade-in\"\n data-topo-hub-fade-delay={0}\n style={{ cursor: 'pointer' }}\n // Stop pointerdown from reaching the SVG pan handler — same\n // reason as the node <g>: a captured pointer makes Chromium\n // fire the follow-up click on the SVG, not this group.\n onPointerDown={(e) => e.stopPropagation()}\n onMouseEnter={() => setHoveredHub(true)}\n onMouseLeave={() => setHoveredHub(false)}\n // Round 52 / Loop: hub is the visual anchor but had no click\n // action — users discovered fit-to-content only via the `f`\n // key or the chrome button. Wire the hub to fitView() so the\n // most prominent element in the canvas is also the \"re-\n // center\" affordance. A click-ripple keyed by ts confirms\n // the gesture; the hub <title> now ends with a hint.\n onClick={() => {\n fitView();\n setClickRipple({ ts: Date.now(), x: cx, y: cy, r0: 18, color: isLight ? '#059669' : '#10b981' });\n setTimeout(() => setClickRipple(prev => prev && prev.x === cx && prev.y === cy ? null : prev), 600);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n fitView();\n setClickRipple({ ts: Date.now(), x: cx, y: cy, r0: 18, color: isLight ? '#059669' : '#10b981' });\n setTimeout(() => setClickRipple(prev => prev && prev.x === cx && prev.y === cy ? null : prev), 600);\n }\n }}\n >\n {/* Round 43 / Loop: hub `<title>` summary — hovering the\n central glow now answers \"what is this?\" with a one-line\n fleet snapshot. Duplicates the header chips by design;\n hovering the most visually prominent element is the\n most natural impulse, so satisfy it where the cursor\n already is. Falls clean: omits sub-clauses when their\n counts are zero. R52 appends a \"click to fit view\" hint.\n */}\n <title>{(() => {\n const total = sessions.length;\n const parts = [`Network hub`, `${total} session${total === 1 ? '' : 's'}`];\n if (onlineNodes.length > 0) parts.push(`${onlineNodes.length} online`);\n if (workingCount > 0) parts.push(`${workingCount} working`);\n if (flowLinks.length > 0) parts.push(`${flowLinks.length} active link${flowLinks.length === 1 ? '' : 's'}`);\n return parts.join(' · ') + '\\nclick to fit view';\n })()}</title>\n {/* grounding halo — breathes in opacity, no expansion.\n R84: breath amplitude + tempo reflect workingCount. An\n idle fleet keeps the original gentle 0.32→0.52 (light) /\n 0.08→0.16 (dark) cycle over 4s — \"heart at rest\". As\n working sessions accumulate, the peak climbs and the\n period shortens, capped at 2.4s so it never feels\n manic. Quiet fleets see zero change; busy fleets feel\n the canvas working. */}\n {(() => {\n // Bucket workingCount to keep the visual feedback discrete\n // rather than continuous: 0 / 1-2 / 3-5 / 6+. Three buckets\n // are enough — finer gradations are imperceptible.\n const busy = workingCount === 0 ? 0\n : workingCount <= 2 ? 1\n : workingCount <= 5 ? 2\n : 3;\n const peakLight = [0.52, 0.58, 0.65, 0.72][busy];\n const peakDark = [0.16, 0.20, 0.26, 0.32][busy];\n const troughLight = 0.32;\n const troughDark = 0.08;\n const dur = [4.0, 3.2, 2.7, 2.4][busy];\n const valuesLight = `${troughLight};${peakLight};${troughLight}`;\n const valuesDark = `${troughDark};${peakDark};${troughDark}`;\n return (\n <circle\n cx={cx} cy={cy} r=\"18\"\n fill={isLight ? '#d1fae5' : '#10b981'}\n opacity={isLight ? 0.42 : 0.12}\n data-hub-busyness={busy}\n /* Round 253 / Loop: hub grounding halo fill transition\n for theme toggle. Pre-R253 the base fill (cyber\n #10b981 ↔ light #d1fae5) snapped while R244's SMIL\n animate on opacity continued running. CSS fill\n transition is independent of the SMIL animate\n (different attributes), so they compose without\n conflict. */\n style={{ transition: 'fill 200ms ease-out' }}\n >\n {/* Round 244 / Loop: hub grounding halo breath gets\n ease-in-out keySplines, matching the active-node\n pulse (R243) treatment. Pre-R244 default linear\n calcMode marched opacity at constant velocity\n through the 3-value trough→peak→trough bounce —\n mechanical pacing for a 'heartbeat at rest'\n visual. R244 adds calcMode='spline' + keyTimes\n '0;0.5;1' + keySplines '0.42 0 0.58 1' ×2 (CSS\n ease-in-out on both halves), so the breath\n decelerates near the troughs AND the peak —\n lingers briefly at each extreme like a real\n heart-rest cycle. Same SMIL-easing family as\n R227 (click ripple) / R228 (edge ping+pulse) /\n R243 (active-node pulse). The breath family\n is now 2 surfaces deep at this single hub\n element — R84 amplitude/tempo bucket + R244\n curve shape. */}\n {!reducedMotion && (\n <animate\n attributeName=\"opacity\"\n values={isLight ? valuesLight : valuesDark}\n dur={`${dur}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.42 0 0.58 1;0.42 0 0.58 1\"\n />\n )}\n </circle>\n );\n })()}\n {/* core — 20px diameter, larger inner highlight reads as a \"lit lamp\"\n Round 248 / Loop: hub center core gets a fill transition.\n Pre-R248 the core circle (the visual anchor at the centre\n of the canvas, fill=isLight ? '#059669' emerald-600 :\n '#10b981' emerald-500) hard-flipped on theme toggle —\n the most visually prominent element on the canvas\n snapping while everything else (R244 halo / R241 hub\n spokes / R246 label cards / R247 side panels) eased.\n Inline transition closes the gap. data-topo-hub-core\n attr added for test introspection (the parent <g> at\n line 3587 has data-topo-hub but the core specifically\n is the canvas anchor). */}\n <circle\n cx={cx} cy={cy} r=\"10\"\n fill={isLight ? '#059669' : '#10b981'}\n data-topo-hub-core\n style={{ transition: 'fill 200ms ease-out' }}\n />\n {/* R130 / Loop: when workingCount > 0, the decorative inner\n highlight gets replaced with the workingCount digit. The\n R84 busyness breath already encodes the same metric\n through motion — adding the digit gives it a second\n visual channel right at the canvas's focal point. A\n user glancing at the hub now sees both \"the network is\n pulsing\" (motion) AND \"3 agents are working\" (digit)\n without having to scan the chip row or panels.\n\n Geometry: text at (cx, cy) with fontSize 11 monospace\n + fontWeight 700 sits inside the r=10 core (a 2-digit\n 12 reads ~12 px wide × 11 px tall, well inside the\n 20-px diameter core). Centered vertically via dy=\n \"0.36em\" — the standard SVG trick for text-vertical-\n center without measuring fontMetrics.\n\n pointerEvents:none so the digit can't intercept the\n hub click (R52 fit-to-view still fires).\n\n workingCount=0 falls through to the existing\n decorative highlight so the hub never looks empty. */}\n {/* Round 213 / Loop: hub centre crossfades the workingCount\n digit and the R130 decorative highlight when count\n crosses zero, instead of mount/unmount snap. Pre-R213 a\n fleet going from idle (workingCount=0, highlight circle)\n to first-working-node (workingCount=1, digit \"1\") swapped\n the elements in a single frame — visible flash at the\n hub's focal point. R213 uses the always-mount + opacity-\n gate pattern (R181/R182/R183 family) so both render\n concurrently and crossfade via opacity transitions.\n Geometry already overlaps (both centred on cx,cy with\n r=5 / digit-bbox ~7×11), so the dual-render adds zero\n layout cost. Reduced-motion users see a 0ms duration\n via the R29 globals.css blanket override. */}\n {/* digit (visible when workingCount > 0) */}\n <text\n x={cx} y={cy}\n textAnchor=\"middle\"\n dy=\"0.36em\"\n fill={isLight ? '#d1fae5' : '#ecfdf5'}\n fontSize=\"11\"\n fontFamily=\"monospace\"\n fontWeight=\"700\"\n opacity={workingCount > 0 ? 1 : 0}\n data-topo-hub-working-count={workingCount}\n data-topo-hub-working-count-hovered={hoveredHub ? 'true' : 'false'}\n data-topo-hub-working-count-visible={workingCount > 0 ? 'true' : 'false'}\n // Round 209 / Loop: hub workingCount digit scales 1.0 →\n // 1.08 on hub-hover, matching R177's r 14→17 ring grow.\n // Pre-R209 hovering the hub grew the ring while the\n // focal-point digit at the centre stayed planted — the\n // gesture lifted only half the structure. R209 ties the\n // digit's scale into the same hoveredHub state R177\n // already drives, so ring + digit rise as one unit.\n // transform-box: fill-box + transform-origin: center\n // anchors the scale to the digit's own bbox (same\n // idiom R184 reset-spin + R186 chrome-pop use for\n // SVG icon transforms). 200ms matches R167 node-ring\n // stroke-width interpolation pace. Reduced-motion users\n // skip the scale via the !reducedMotion gate (R29 a11y).\n // Round 225 / Loop: tabular-nums on hub digit — info-\n // density sibling to R224's edge badge tabular-nums.\n // Same physics: when workingCount crosses the 9 → 10\n // boundary the textAnchor='middle' centering jitters\n // ~3-4px because monospace fonts still have width\n // variance at the digit-vs-control boundary. Tabular\n // locks digit width so the focal point stays planted\n // through every count change. Pure visual tightening;\n // no test trap (computed font-variant-numeric resolves\n // to the keyword 'tabular-nums' verbatim).\n /* Round 253 / Loop: append fill 200ms to the hub\n digit transition list — theme toggle (cyber #ecfdf5\n ↔ light #d1fae5) was the last hub-area snap. */\n style={{\n pointerEvents: 'none',\n transform: !reducedMotion && hoveredHub ? 'scale(1.08)' : 'scale(1)',\n transformBox: 'fill-box',\n transformOrigin: 'center',\n transition: 'transform 200ms ease-out, opacity 300ms ease-out, fill 200ms ease-out',\n fontVariantNumeric: 'tabular-nums',\n }}\n >\n {workingCount}\n </text>\n {/* decorative highlight (visible when workingCount === 0) */}\n <circle\n cx={cx} cy={cy} r=\"5\"\n fill=\"#d1fae5\"\n opacity={workingCount > 0 ? 0 : 0.9}\n data-topo-hub-highlight\n data-topo-hub-highlight-visible={workingCount > 0 ? 'false' : 'true'}\n style={{\n pointerEvents: 'none',\n transition: 'opacity 300ms ease-out',\n }}\n />\n {/* R115 / Loop: hover hint ring. Stroke-only circle at r=14\n that fades in when the hub is hovered — the same idea\n R44 used for node avatars (group-hover stroke). r=14\n sits comfortably outside the r=10 core and INSIDE the\n r=18 grounding halo, so the hover indicator is fully\n contained within the existing hub footprint (no bbox\n growth, overlap test unchanged). pointerEvents:none so\n the hint can't intercept the click that produced it. */}\n {/* Round 177 / Loop: hub hover ring picks up the same\n \"lift on hover\" gesture R164 added to the edge midpoint\n badge (r=9→10.5). Pre-R177 the ring faded opacity 0→0.7\n on hover but stayed static at r=14. Adding r=14→17 on\n hover gives the gesture extra weight — the hub responds\n more confidently. Stays within the r=18 halo bbox (no\n geometry growth), so the R51 overlap-test guard rails\n still hold. strokeWidth=1.5 is the offline-node\n sentinel but the overlap-test selector is gated to\n `g[data-node]` ancestors — this hub-internal circle\n is invisible to that probe. transition list grows `r`\n alongside opacity so both ease in concert; the lift\n feels like one continuous gesture. Seventh surface in\n the hover-elevation family (R51 nodes, R135 panels,\n R142 group boxes, R143/R144 rows, R164 edge badges,\n R177 hub ring). prefers-reduced-motion respected via\n R29 globals.css blanket. */}\n <circle\n cx={cx} cy={cy}\n r={hoveredHub ? 17 : 14}\n fill=\"none\"\n stroke={isLight ? '#059669' : '#10b981'}\n strokeWidth=\"1.5\"\n opacity={hoveredHub ? (isLight ? 0.85 : 0.7) : 0}\n data-topo-hub-hover-ring\n data-topo-hub-hover-ring-radius={hoveredHub ? 17 : 14}\n /* Round 253 / Loop: hub hover ring also gets stroke\n transition for theme toggle (cyber #10b981 ↔ light\n #059669). The opacity + r transitions stay for hover\n lift; stroke closes the theme-snap. */\n style={{\n pointerEvents: 'none',\n transition: 'opacity 180ms ease-out, r 180ms ease-out, stroke 200ms ease-out',\n }}\n />\n </g>)}\n\n {/* agent nodes */}\n {[...onlineNodes, ...offlineNodes].map((session, nodeIdx) => {\n const pos = nodePositions[session.alias];\n if (!pos) return null;\n\n const sseCountFor = (session.network_id ? sseSessions[`${session.network_id}:${session.alias}`] : undefined) ?? sseSessions[session.alias];\n const isOnline = session.status !== 'offline' || !!sseCountFor;\n const status = nodeStatus(session, isOnline, isLight);\n const isActive = activeAliases.has(session.alias);\n // #113: node size scales with the S/M/L control; halos, labels,\n // badge and avatar all derive from `radius` so they follow.\n const radius = Math.round((isOnline ? 26 : 18) * nodeScale);\n // Round 109/110 (Vincent 4582 + 4583 P0): at high node counts\n // the 100px opaque label cards overlapped each other and\n // covered neighbouring avatars. But hiding text entirely went\n // too far (\"还是得有文字\"). So: below the density threshold the\n // full name+status card shows always; above it each node shows\n // a lightweight plain-text alias (no opaque card → can't\n // occlude an avatar), and the full card appears only for the\n // hovered node or once zoomed in past 1.4×.\n const showFullLabel = !denseLayout || hoveredAlias === session.alias || view.zoom >= 1.4;\n // Round 8: when a node in another group is hovered, fade this\n // one. Same-group nodes (incl. singletons matching the hovered\n // alias) stay full. Pure visual focus, geometry unchanged.\n const inFocus = !activeGroup || (groupKeys[session.alias] ?? session.alias) === activeGroup;\n // R72: in ring layout, classify the node into a tier by its\n // distance from hub centre so the first-paint stagger can\n // emanate inner → outer instead of running clockwise. Grid\n // layout doesn't have a radial structure; it keeps R9's pure\n // nodeIdx stagger. Thresholds bracket the actual tier radii\n // (single 220, dual 175/260, triple 145/215/285, offline 325+).\n let tierIdx = 0;\n if (layout === 'ring') {\n const p = nodePositions[session.alias];\n if (p) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n tierIdx = d < 195 ? 0 : d < 270 ? 1 : d < 310 ? 2 : 3;\n }\n }\n\n return (\n <g\n key={session.alias}\n data-node={session.alias}\n data-tier-idx={layout === 'ring' ? tierIdx : -1}\n // R151 / Loop: node a11y compliance — match the pattern\n // R116 / R139 / R140 / R148 / R149 applied to other\n // interactive surfaces. Node <g> has been clickable for\n // chat since R45 but tab-unreachable + screen-reader-\n // unannounced. role=\"button\" + tabIndex=0 + aria-label\n // expose it; aria-pressed reflects chat-target state so\n // SR users know which node currently has the popover\n // open. onKeyDown for Enter / Space fires the same\n // setChatAlias path as onClick. preventDefault on\n // Space stops the SVG canvas from scrolling.\n role=\"button\"\n tabIndex={0}\n aria-pressed={chatAlias === session.alias}\n aria-label={`Chat with ${session.alias} (${session.status})`}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setChatAlias(session.alias);\n setClickRipple({\n ts: Date.now(),\n x: pos.x, y: pos.y, r0: radius,\n color: status.primary,\n });\n setTimeout(() => setClickRipple(prev =>\n prev && Date.now() - prev.ts >= 590 ? null : prev), 600);\n }\n }}\n // Round 3 / Loop: `anet-fade-in` runs once when the <g>\n // mounts — a new session entering the fleet (or the topology\n // first rendering) eases in instead of popping. Re-renders of\n // an existing node don't re-trigger (React preserves the <g>\n // via the alias key), so status changes don't flicker. The\n // global prefers-reduced-motion sweep already neutralises it.\n className=\"group transition-opacity anet-fade-in anet-topo-svg-focus\"\n style={{\n cursor: 'pointer',\n // Round 17 / Loop: offline nodes drop to 0.6 at rest so\n // online nodes pop without losing the offline-as-ghost\n // information. The dashed stroke + smaller radius already\n // say \"offline\"; dimming the whole group strengthens the\n // online-vs-offline hierarchy at first glance. Exempt the\n // chat-focused node — if the user explicitly opened a\n // popover targeting an offline alias, that node stays\n // full-brightness so the focus ring + popover read as one\n // selected thing rather than a dimmed selection. Group-\n // hover fade (Round 8) still wins when inFocus is false.\n // Round 49 / Loop: edge-hover endpoint highlight composes\n // OVER the inFocus/online formula — a non-endpoint node\n // dims to 0.28 (just below the inFocus 0.32 to read as a\n // stronger \"not relevant\" signal), endpoints keep their\n // base opacity. chatAlias still wins to keep the focus\n // ring legible if the user clicked through.\n // Round 55 / Loop: legend-status hover composes on the\n // SAME level as edge-hover-endpoint. Non-matching nodes\n // dim to 0.28; matching nodes stay at their base.\n // activeStatus matches: working = status==='working',\n // idle = online but not working, offline = !isOnline.\n // Round 60 / Loop: activeStatus = hoveredStatus ?? pinnedStatus\n // so the pressure-bar segment pins (R60) and the legend\n // row hover (R55) feed the same branch.\n // Round 80 / Loop: vendor-letter hover composes ABOVE the\n // activeStatus branch so hovering the vendor chip's `C`\n // dims everything except C-vendor nodes. Same dim value\n // (0.28) as edge-endpoint and status filters — visually\n // consistent. Vendor lookup uses the same vendorForModel\n // helper the avatar render uses, keyed by initial so the\n // chip and the avatar always agree on grouping.\n opacity: hoveredEdgeEndpoints && !hoveredEdgeEndpoints.has(session.alias) && chatAlias !== session.alias\n ? 0.28\n : activeVendor && chatAlias !== session.alias && (() => {\n const v = vendorForModel(session.model);\n const initial = v.id === 'unknown' ? '?' : v.initial;\n return initial !== activeVendor;\n })()\n ? 0.28\n : activeStatus && chatAlias !== session.alias && !(\n activeStatus === 'working' ? session.status === 'working'\n : activeStatus === 'idle' ? (isOnline && session.status !== 'working')\n : /* offline */ !isOnline\n )\n ? 0.28\n : !inFocus\n ? 0.32\n : chatAlias === session.alias\n ? 1\n : isOnline ? 1 : 0.6,\n // Round 9 / Loop: stagger the anet-fade-in so the topology\n // reveals as a wave on first paint instead of one big pop.\n // Cap at 24 indices (≈600ms tail) so 50-node fleets still\n // finish revealing within a beat. CSS animation-delay only\n // applies during the keyframe — re-renders without a new\n // mount (same alias key) don't replay, so status changes\n // never trigger the stagger again.\n // Round 72 / Loop: in ring layout, stagger by tier radius\n // so the topology emanates from the hub outward — inner\n // tier at 0ms, middle at ~180ms, outer at ~360ms, offline\n // at ~540ms. A small within-tier offset (nodeIdx % 6 * 25)\n // adds variety so each ring rotates in instead of all-at-\n // once popping. Grid keeps R9's pure nodeIdx stagger.\n animationDelay: layout === 'ring'\n ? `${tierIdx * 180 + (nodeIdx % 6) * 25}ms`\n : `${Math.min(nodeIdx, 24) * 25}ms`,\n // Round 51 / Loop: hover micro-lift. The label already\n // lifts on group-hover (R26). The node body — circle,\n // avatar, status ring — stays planted, so the gesture\n // reads \"label moves, body doesn't\". Now the whole <g>\n // translates -2px when hovered: avatar + ring + label\n // move as one unit. 2 px is well inside the inter-row\n // gap (cellH headroom ≥22), and CSS transform on SVG\n // <g> never affects the overlap-test geometry (the\n // test never simulates hover). useReducedMotion drops\n // the lift to 0 for prefers-reduced-motion users.\n transform: !reducedMotion && hoveredAlias === session.alias ? 'translateY(-2px)' : undefined,\n transition: 'transform 180ms cubic-bezier(0.4,0,0.2,1)',\n }}\n // Stop the pointerdown from reaching the SVG pan handler: the\n // SVG calls setPointerCapture, and a captured pointer makes\n // Chromium fire the follow-up `click` on the SVG instead of\n // this node — so without this the node's onClick never runs.\n // Side effect (intended): you pan from empty canvas, not by\n // grabbing a node.\n onPointerDown={(e) => e.stopPropagation()}\n // Track hover globally (not just under denseLayout) so the\n // Round 8 group-focus fade works at any fleet size.\n onPointerEnter={() => setHoveredAlias(session.alias)}\n onPointerLeave={() => setHoveredAlias(prev => (prev === session.alias ? null : prev))}\n onClick={() => {\n setChatAlias(session.alias);\n // Round 14 ripple — capture position, radius and status\n // colour at click time so the one-shot circle is self-\n // contained (no re-render-time recomputation needed).\n setClickRipple({\n ts: Date.now(),\n x: pos.x, y: pos.y, r0: radius,\n color: status.primary,\n });\n setTimeout(() => setClickRipple(prev =>\n prev && Date.now() - prev.ts >= 590 ? null : prev), 600);\n }}\n >\n {/* Issue #96: native hover tooltip — \"Vendor · model · Runtime\".\n Falls back to just the alias when the node reports no\n model/runtime.\n Round 33 / Loop: cwd line answers \"what is this agent on?\".\n Round 34 / Loop: for offline nodes, append \"last seen: 6m ago\"\n so the operator knows whether to wait or chase. Online nodes\n skip the line (Round 27's 1 h ghost age-out means anything\n still online has heartbeated recently — the line would just\n be noise). Accepts both ISO (\"…T06:00:28Z\") and SQL-style\n (\"… 06:00:28\" assumed UTC) formats. */}\n {(() => {\n // Round 35 / Loop: TZ-safe parsing via the shared lib helper\n // — same parseHubTime is used by isGhost above (Round 38\n // factored it to app/lib/time.ts so both paths interpret SQL\n // bare timestamps as UTC on every browser).\n const lastSeen = !isOnline && session.last_seen_at ? relativeAgo(session.last_seen_at) : null;\n // Round 98 / Loop: enrich the node tooltip with status,\n // group membership, and inbound/outbound flow counts —\n // same info-density spirit as R97 active-filter pill\n // tooltips. Hovering any node now answers \"what is this\n // and how does it sit in the topology\" without forcing\n // a click into the chat popover. Group line only shows\n // when the alias is part of a multi-member band (R106\n // prefix grouping); singletons skip it. Flow line only\n // when at least one direction has count > 0.\n const groupKey = groupKeys[session.alias];\n const groupMembers = groupKey\n ? Object.values(groupKeys).filter(k => k === groupKey).length\n : 1;\n const groupLine = groupMembers > 1 ? `group: ${groupKey} · ${groupMembers}` : null;\n // R147 / Loop: node tooltip extends R98's flow summary\n // with the actual sender / receiver aliases. R98 told you\n // \"12 in / 5 out\" but not WHO. The R97 idiom — anywhere\n // the UI shows \"N\" should hover-explain WHICH N — applied\n // to filter pills, group labels, vendor letters, pressure\n // segments, recent-row text, active-links chip; the node\n // title was the last surface still showing only the\n // aggregate. Direction-tagged: senders are who's\n // messaging THIS node (inbound), receivers are who this\n // node is messaging (outbound). 6-truncate + \"+N more\"\n // matches the pattern other tooltips use so a 30-flow\n // node doesn't paint a 30-line tooltip.\n let flowIn = 0, flowOut = 0;\n const sendersMap = new Map<string, number>(); // alias → count, inbound\n const receiversMap = new Map<string, number>(); // alias → count, outbound\n for (const fl of flowLinks) {\n if (fl.from === session.alias) {\n flowOut += fl.count;\n receiversMap.set(fl.to, (receiversMap.get(fl.to) ?? 0) + fl.count);\n }\n if (fl.to === session.alias) {\n flowIn += fl.count;\n sendersMap.set(fl.from, (sendersMap.get(fl.from) ?? 0) + fl.count);\n }\n }\n const fmtPeers = (m: Map<string, number>) => {\n const pairs = [...m.entries()].sort((a, b) => b[1] - a[1]);\n const preview = pairs.slice(0, 6).map(([a, n]) => `${a} (${n})`).join(', ');\n const suffix = pairs.length > 6 ? ` + ${pairs.length - 6} more` : '';\n return preview + suffix;\n };\n const flowLine = (flowIn + flowOut) > 0 ? `flows: ${flowIn} in / ${flowOut} out` : null;\n const sendersLine = sendersMap.size > 0 ? `← from: ${fmtPeers(sendersMap)}` : null;\n const receiversLine = receiversMap.size > 0 ? `→ to: ${fmtPeers(receiversMap)}` : null;\n return (\n <title>{[\n `${session.alias} · ${session.status}`,\n identityLine(session.model, session.runtime),\n groupLine,\n session.project_dir ? `cwd: ${session.project_dir}` : null,\n lastSeen ? `last seen: ${lastSeen}` : null,\n flowLine,\n sendersLine,\n receiversLine,\n ].filter(Boolean).join('\\n')}</title>\n );\n })()}\n {/* Round 2 / Loop: hover ring — a thin outer stroke that fades\n in when the cursor enters the node, signalling clickability\n (real-user feedback for the chat-popover open). Pure CSS via\n Tailwind group-hover, so it costs nothing per frame and\n respects prefers-reduced-motion via the global media query. */}\n <circle\n cx={pos.x}\n cy={pos.y}\n r={radius + 12}\n fill=\"none\"\n stroke={status.primary}\n // strokeWidth must NOT be 1.5 (offline status ring) or 3\n // (online status ring) — the overlap test selects by those\n // exact widths and would mis-count this invisible hover\n // ring as a node footprint.\n strokeWidth=\"2\"\n className=\"opacity-0 group-hover:opacity-70 transition-opacity duration-150\"\n style={{ pointerEvents: 'none' }}\n />\n {/* Round 11 / Loop: chat-focus ring — when the ChatPopover is\n open targeting this node, anchor a persistent ring around\n it so the floating popover visibly links back to its source\n node. Static (not pulsing) so it reads as \"selected state\"\n rather than \"this node is active\". strokeWidth=2.5 stays\n clear of the overlap-test selectors (1.5 / 3). Sits just\n outside the halo radius+8 so it never overlaps a neighbour\n (halos already pack flush in dense grids). */}\n {/* R51 chat-target ring. R120 / Loop: gentle SMIL\n breath on the ring's opacity (±0.1 over 3s) when\n chat is open + !reducedMotion. Says \"active session\n here\" continuously without animation noise — the\n ring only appears for one node at a time (the\n chatAlias), so it never competes with R84 hub\n busyness or R112 working halo for attention.\n\n Round 183 / Loop: 7th surface in the smooth-pin-\n mirror family. Pre-R183 the ring was conditionally\n mounted on `chatAlias === session.alias`; the\n className `transition-opacity duration-200` never\n fired because the element didn't exist before\n mount. Always-mounted now with opacity gated by\n isChat — the CSS transition fires cleanly on\n chat-close (smooth fade-out). The `<animate>`\n SMIL stays gated by `!reducedMotion && isChat`\n so it only runs for the active chat target; when\n chat is closed, SMIL unmounts and opacity reverts\n to attribute (0) → CSS transitions down smoothly.\n On chat-OPEN the SMIL takes over per spec, so the\n fade-in is snappier than the fade-out — acceptable\n because the user explicitly clicked the node.\n\n strokeWidth=2.5 is not a R51 sentinel value\n (sentinels are 1.5/3 inside g[data-node]), so the\n ring is invisible to the overlap-test selector\n even when always-mounted. */}\n {/* Round 242 / Loop: extend the chat-target ring's\n transition list to include stroke + filter.\n Pre-R242 only `opacity` eased (R183 200ms): a\n chat-target node going working → idle hard-\n flipped the ring's stroke colour (status.primary\n green → teal) in one frame even though the rest\n of the ring was a smooth presence. Filter (glow\n on cyber, none on light) also snapped on chat\n toggle AND on theme switch.\n\n Add `stroke 200ms ease-out` + `filter 200ms\n ease-out` so the colour and glow both ease at\n the same cadence as the opacity gate. Same\n idiom R167 (node status-ring) uses for\n coordinated colour-easing on status flip;\n R242 brings the chat-target ring up to that\n bar. */}\n {(() => {\n const isChat = chatAlias === session.alias;\n return (\n <circle\n cx={pos.x}\n cy={pos.y}\n r={radius + 14}\n fill=\"none\"\n stroke={status.primary}\n strokeWidth=\"2.5\"\n opacity={isChat ? (isLight ? 0.85 : 0.95) : 0}\n filter={!isLight && isChat ? 'url(#topo-glow)' : undefined}\n style={{ pointerEvents: 'none', transition: 'opacity 200ms ease-out, stroke 200ms ease-out, filter 200ms ease-out' }}\n data-chat-target-ring\n data-chat-target-active={isChat ? 'true' : 'false'}\n data-chat-target-breath={!reducedMotion && isChat ? 'on' : 'off'}\n >\n {!reducedMotion && isChat && (\n <animate\n attributeName=\"opacity\"\n values={isLight ? '0.72;0.95;0.72' : '0.82;1;0.82'}\n dur=\"3s\"\n repeatCount=\"indefinite\"\n />\n )}\n </circle>\n );\n })()}\n {/* Round 243 / Loop: active-node pulse (the breathing\n aura ring at r=radius+14 fill=status.primary, shown\n on nodes participating in a recent flow) gains TWO\n polishes:\n\n 1) Always-mount + opacity-gate wrapper <g>. Pre-\n R243 the circle conditionally mounted on\n isActive — when a node joined a flow, the pulse\n snap-appeared at radius+8 (first SMIL phase\n value), and when the flow stopped, snap-\n disappeared. R243 keeps the SMIL animation\n running continuously inside an opacity-gated\n <g>; isActive flips the WRAPPER opacity\n 1↔0 with a 300ms ease-out transition so the\n pulse fades in/out at its current phase\n instead of restarting from +8. The reduced-\n motion gate stays at the conditional render\n level — reduced-motion users see no pulse at\n all (no point without the animation).\n\n 12th surface in the always-mount-opacity-gate\n family (R181/R182/R183/R213×2/R214/R215/R221/\n R222/R223/R237/R243).\n\n 2) SMIL ease-in-out keySplines on both r and\n opacity animates. Pre-R243 default linear\n calcMode produced a constant-velocity breath\n (radius marched +8 → +22 at fixed dr/dt;\n opacity dimmed 0.12 → 0.02 at fixed dα/dt) —\n mechanical, not organic. calcMode='spline'\n + keyTimes='0;0.5;1' + per-segment keySplines\n '0.42 0 0.58 1' (canonical CSS ease-in-out)\n both ways gives a settled breath: slow at\n both endpoints (small and large radius / lit\n and dim opacity), fast through the middle.\n Same SMIL-easing family R227 / R228 already\n inhabits at the click ripple + edge ping +\n pulse. */}\n {!reducedMotion && (\n <g\n opacity={isActive ? 1 : 0}\n data-node-pulse={session.alias}\n data-node-pulse-active={isActive ? 'true' : 'false'}\n style={{ transition: 'opacity 300ms ease-out' }}\n >\n <circle cx={pos.x} cy={pos.y} r={radius + 14} fill={status.primary} opacity={isLight ? 0.08 : 0.12}>\n <animate\n attributeName=\"r\"\n values={`${radius + 8};${radius + 22};${radius + 8}`}\n dur=\"2.4s\"\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.42 0 0.58 1;0.42 0 0.58 1\"\n />\n <animate\n attributeName=\"opacity\"\n values={isLight ? '0.12;0.02;0.12' : '0.18;0.04;0.18'}\n dur=\"2.4s\"\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.42 0 0.58 1;0.42 0 0.58 1\"\n />\n </circle>\n </g>\n )}\n {/* Round 4 / Loop: transition-[fill,stroke,opacity] smooths\n status colour changes so idle↔working↔offline doesn't snap\n — task replies / node-rename / SSE updates ease in.\n Round 112 / Loop: working nodes get a subtle halo breath\n (±0.12 opacity at 3s cycle) so the eye can find \"what's\n busy\" at a glance without scanning chips. Idle + offline\n halos stay flat — they don't need to demand attention.\n R84 hub-center breath stays the loudest \"fleet busyness\"\n signal; this one is quieter, per-node. SMIL `<animate>`\n inside the circle, gated by reducedMotion. */}\n {/* Round 226 / Loop: working-halo breath gets per-node\n phase stagger. Pre-R226 every working node's halo\n pulsed in lockstep — all 0.73→0.92→0.73 starting at\n the same instant — which on a fleet of 4+ working\n agents reads as one mechanical metronome rather\n than an organic group of breathing entities.\n\n SMIL `<animate>` accepts negative `begin` to offset\n the cycle backwards in time (the animation starts\n mid-cycle on first paint). Using\n `(nodeIdx * 0.37) % 3` gives a deterministic,\n well-distributed offset across the 3s period —\n the same golden-ratio-ish 0.37 fraction R103 uses\n for particle phase stagger on edges. 0.37 doesn't\n line up for any small N (4 nodes → offsets 0,\n 0.37, 0.74, 1.11 — evenly spread, never\n coincident).\n\n Side benefit: when a new agent joins a busy fleet\n its halo phase is determined by its position in\n the order array, not \"when it joined\" — so a\n re-render doesn't reshuffle breath phases. Order\n is stable (R-onlineNodes sort), so the canvas\n feels calm rather than jittery on each refresh.\n\n Reduced-motion users skip the animate entirely\n (gate unchanged). Halo opacity transition on the\n parent stays for status flips. data-node-halo-\n breath-offset surfaces the chosen offset for\n test introspection. */}\n <circle\n cx={pos.x}\n cy={pos.y}\n r={radius + 8}\n fill={status.halo}\n opacity={isOnline ? (isLight ? 0.85 : 0.65) : (isLight ? 0.4 : 0.25)}\n className=\"transition-[fill,opacity] duration-300 ease-out\"\n data-node-halo-breath={!reducedMotion && session.status === 'working' ? 'on' : 'off'}\n data-node-halo-breath-offset={\n !reducedMotion && session.status === 'working'\n ? ((nodeIdx * 0.37) % 3).toFixed(3)\n : undefined\n }\n >\n {/* Round 278 / Loop: per-node working halo breath\n (R112+R226+R244 family) RETIRED per Vincent\n 5214/5215-5217 simplification ask (减法 cut #4\n after R275 chip-row, R276 orbit, R277 legend).\n\n The breath was: each working agent's halo pulses\n 0.73→0.92→0.73 (cyber 0.53→0.78→0.53) at 3 s\n cycle, R226-staggered per-node, R244-eased. For\n a 4-working fleet that's 4 simultaneous SMIL\n breaths competing with the hub-halo breath\n (R244 hub) for the \"fleet busyness\" visual\n signal.\n\n The signal is info-redundant: the hub-halo\n breath ALREADY conveys \"the network is alive\n and busy\"; per-node halo breath duplicates it\n at 4× volume. Plus working nodes are ALREADY\n distinguished by their halo color (status.halo\n green/teal/slate via R12 trio) — the static\n halo carries identity, the moving breath was\n decorative motion on top.\n\n R278 gates the SMIL animate with `false &&` so\n the code remains for rollback. Halo opacity\n stays at the BASE (non-breathing) values via\n the parent circle's `opacity` attr (0.85/0.65/\n 0.4/0.25 from R12 + isOnline gate). Working\n nodes still show green halos; they just don't\n pulse.\n\n Net: -4 SMIL animations on canvas for typical\n 4-working fleet. Combined with R276 orbit\n retirement (-4) and the hub halo breath kept\n as the SOLE \"fleet busyness\" motion signal,\n the canvas reads quieter. R226 + R244 per-node\n stagger / ease constants are dead code post-\n R278 (acceptable — family retires together). */}\n {false && !reducedMotion && session.status === 'working' && (\n <animate\n attributeName=\"opacity\"\n values={isLight ? '0.73;0.92;0.73' : '0.53;0.78;0.53'}\n dur=\"3s\"\n begin={`-${((nodeIdx * 0.37) % 3).toFixed(3)}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.42 0 0.58 1;0.42 0 0.58 1\"\n />\n )}\n </circle>\n {/* Round 111 / Loop: edge-endpoint emphasis ring. R49\n already keeps endpoint nodes at opacity 1 while\n others dim when an edge is hovered, but the\n endpoints had no POSITIVE indicator — they just\n \"stayed bright\". An accent stroke at r=radius+7\n (just inside the halo's r=radius+8 bbox so we\n don't grow the overlap footprint) clearly says\n \"these are the two participants in this flow\".\n pointerEvents:none so the node hitbox stays alive.\n\n Round 182 / Loop: the ring used to mount/unmount\n on every edge hover, snapping despite the\n opacity transition on the style. Always-mount\n with opacity gated by hoveredEdgeEndpoints — same\n pattern R181 used for the legend pin ring. The\n transition now actually fires on hover entry\n and exit. 6th surface in the smooth-pin-mirror\n family (R165/R180/R181 + this round).\n\n strokeWidth=1.6 (was 1.5) deliberately escapes\n the R51 overlap-test sentinel `circle[stroke-\n width=\"1.5\"]`: an always-mounted r=radius+7 ring\n inside g[data-node] would otherwise be picked\n before the actual status ring (r=radius) by\n querySelector document order, breaking the\n test's node-bbox read. 1.5 → 1.6 is visually\n imperceptible (6.7% thicker) but the exact-\n string CSS attribute selector no longer\n matches. */}\n {(() => {\n const isEndpoint = hoveredEdgeEndpoints && hoveredEdgeEndpoints.has(session.alias);\n /* Round 233 / Loop: endpoint ring picks up a stroke-\n width thicken on edge-hover, completing the hover-\n elevation gesture across the whole edge surface.\n Pre-R233 hovering an edge eased the visible path\n stroke (R166) and lifted the badge r (R164) — but\n the two endpoint rings only faded IN (R182\n opacity gate). Now they ALSO thicken 1.6 → 2.4 on\n hover, in 180ms ease-out matching R164 badge lift.\n The endpoint nodes feel like they \"rise to meet\"\n the edge as the cursor approaches it, instead of\n just appearing.\n\n 1.6 and 2.4 both escape the R51 overlap-test\n sentinels (1.5 / 3 are reserved) — 2.4 sits\n comfortably between, visually 50% thicker than\n baseline so the gesture reads but the radius is\n unchanged (still r=radius+7) so geometry stays\n calm and the topo-overlap-test stays green. 9th\n surface in the hover-elevation family (R51\n nodes / R135 panels / R142 group boxes / R143-\n R144 rows / R164 edge badges / R177 hub ring /\n R229 group-label count brighten / R233 endpoint\n ring stroke-width). data-edge-endpoint-ring-\n stroke-width attr surfaces the chosen value for\n test introspection. */\n return (\n <circle\n cx={pos.x}\n cy={pos.y}\n r={radius + 7}\n fill=\"none\"\n stroke={pal.flowEdge}\n strokeWidth={isEndpoint ? 2.4 : 1.6}\n opacity={isEndpoint ? (isLight ? 0.9 : 0.85) : 0}\n data-edge-endpoint-ring\n data-edge-endpoint-active={isEndpoint ? 'true' : 'false'}\n data-edge-endpoint-ring-stroke-width={isEndpoint ? 2.4 : 1.6}\n style={{ pointerEvents: 'none', transition: 'opacity 180ms ease-out, stroke-width 180ms ease-out' }}\n />\n );\n })()}\n {/* Round 167 / Loop: extend the node status-ring\n transition to include stroke-width — symmetric\n with R165 (pressure-bar width) and R166 (edge\n stroke-width). Pre-R167 only fill+stroke colors\n transitioned smoothly; stroke-width snapped from\n 3 (online) to 1.5 (offline) when a session\n transitioned. With stroke-width in the transition\n list the ring smoothly contracts as a node goes\n offline (or expands when it comes back).\n strokeDasharray stays binary (none ↔ '5 5')\n because dash values don't interpolate cleanly\n between continuous and discrete forms.\n Inline style replaces the Tailwind transition-\n [fill,stroke] className for stable arbitrary\n property compilation. Respects prefers-reduced-\n motion via R29 globals.css blanket override.\n data-node-status-ring exposes this circle for\n test probing — the overlap-test guard on\n stroke-width=\"3\"/\"1.5\" still works against the\n DOM attribute value (React-rendered, not\n interpolated). */}\n <circle\n cx={pos.x}\n cy={pos.y}\n r={radius}\n fill={isOnline ? pal.nodeFill.online : pal.nodeFill.offline}\n stroke={status.primary}\n strokeWidth={isOnline ? 3 : 1.5}\n strokeDasharray={isOnline ? 'none' : '5 5'}\n filter={isOnline && !isLight ? 'url(#topo-glow)' : undefined}\n data-node-status-ring={status.label}\n style={{\n transition: 'fill 300ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out',\n }}\n />\n {/* v0.10.0 Hero 1+2 / §3.F server-health node-ring tint.\n When the host server this agent runs on is in the\n `red` tier (CPU/Mem/Disk worst-of ≥ 85% per\n classifyServer threshold), draw a faint amber outer\n halo at radius+8. strokeWidth=2.5 stays clear of the\n R51 overlap-test sentinels (1.5 = offline status ring,\n 3 = online status ring). pointerEvents:none so the\n halo can't intercept node clicks. Falls back silent\n when host telemetry hasn't shipped yet (hostHealthMap\n is empty until commhub returns telemetry). Composes\n with R209 hover-ring (which sits BETWEEN the avatar\n and this halo on hover — different radii). */\n }\n {(() => {\n const tier = hostHealthMap.get(session.server);\n if (tier !== 'red') return null;\n return (\n <circle\n cx={pos.x}\n cy={pos.y}\n r={radius + 8}\n fill=\"none\"\n stroke={isLight ? '#d97706' : '#fbbf24'}\n strokeWidth=\"2.5\"\n opacity=\"0.6\"\n data-node-server-health=\"red\"\n data-node-server-host={session.server}\n style={{\n pointerEvents: 'none',\n transition: 'stroke 200ms ease-out, opacity 200ms ease-out',\n }}\n />\n );\n })()}\n {/* Issue #96: node \"avatar\" is now driven by the model\n vendor. Decision order:\n 1. ?brand=intern flag, or an intern-aliased node with\n no model field → 书生 coin (preserves #79).\n 2. vendor has a packaged logo asset → that logo image\n (intern-s1-* models land here via vendorForModel).\n 3. known vendor, logo asset not shipped yet → a\n vendor-tinted monogram (spec-mandated fallback).\n 4. unknown vendor / null model → the prefix-group\n hue-hashed initial (#83/#99 behaviour, unchanged). */}\n {(() => {\n const ar = Math.round((isOnline ? 14 : 10) * nodeScale);\n const size = radius * 2;\n const vendor = vendorForModel(session.model);\n const internByAlias = /书生|书小生|intern/i.test(session.alias);\n\n if (isIntern || internByAlias || vendor.logo) {\n return (\n <image\n href={vendor.logo ?? '/intern_avatar.png'}\n x={pos.x - size / 2}\n y={pos.y - size / 2}\n width={size}\n height={size}\n preserveAspectRatio=\"xMidYMid meet\"\n />\n );\n }\n if (vendor.id !== 'unknown') {\n // Known model house, logo asset not in public/vendors/\n // yet — vendor-tinted monogram stands in.\n /* Round 283 / Loop: monogram circle strokeWidth bumps\n 1 → 1.5 per Vincent 5216 \"书生头像风格延续 — 其他\n vendor 头像 plain text/abbreviation 比书生差, polish\n 升级\". Without real vendor logo PNG/SVG assets in\n public/vendors/, the monogram is the visual stand-\n in; bumping its ring weight from 1 to 1.5 narrows\n the visual-quality gap with the 书生 image avatar\n (which is a designed PNG, naturally more\n substantial). The 1px → 1.5px stroke is the same\n weight increment R268 used on the chrome-strip\n border unification — small but perceptible. The\n prefix-group fallback (line ~5172) stays at\n strokeWidth=1 since that's for UNKNOWN vendors\n where less visual weight signals \"we don't know\n what this is\" appropriately. */\n return (\n <>\n <circle cx={pos.x} cy={pos.y} r={ar} fill={vendor.mono.bg} stroke={vendor.mono.ring} strokeWidth=\"1.5\" />\n {/* Round 284 / Loop: known-vendor monogram letter\n swaps fontFamily monospace → system sans-serif.\n Continuation of R283 Vincent 5216 \"vendor 头像\n polish 升级\". A single centered letter does not\n benefit from monospace's digit-alignment\n property — its only effect at this scale is to\n land a slightly thinner, more code-text-like\n glyph. The system stack ('-apple-system',\n 'BlinkMacSystemFont', 'Segoe UI', 'Inter',\n 'sans-serif') picks the OS-preferred designed\n sans-serif, which renders a fuller, more\n \"badge-mark\" letterform — narrowing the\n visual-quality gap with the 书生 PNG (which is\n a hand-designed image). data-monogram-letter\n exposes the element for test probing.\n\n The prefix-group fallback at line ~5197\n INTENTIONALLY stays on monospace — same\n contrast pattern R283 established for ring\n stroke: \"designed glyph\" = known vendor,\n \"code text\" = unknown vendor bucket. */}\n <text\n x={pos.x} y={pos.y} dy=\"0.34em\" textAnchor=\"middle\"\n fill={vendor.mono.text} fontSize={ar}\n fontFamily=\"-apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, sans-serif\"\n fontWeight=\"700\"\n data-monogram-letter={vendor.initial}\n >\n {vendor.initial}\n </text>\n </>\n );\n }\n // Round 106 (issue #83): hue keyed to the prefix group,\n // not the full alias — every 通信* node shares one color.\n const c = aliasAvatarColors(groupKeys[session.alias] || session.alias);\n return (\n <>\n <circle cx={pos.x} cy={pos.y} r={ar} fill={c.bg} stroke={c.ring} strokeWidth=\"1\" />\n <text\n x={pos.x}\n y={pos.y}\n dy=\"0.34em\"\n textAnchor=\"middle\"\n fill={c.text}\n fontSize={ar}\n fontFamily=\"monospace\"\n fontWeight=\"700\"\n >\n {aliasInitial(session.alias)}\n </text>\n </>\n );\n })()}\n {/* Issue #96: runtime badge — small corner glyph marking the\n execution shell (CLI / SDK / HTTP API). Sits bottom-right\n of the avatar; colours kept off the working/idle/offline\n status hues. Absent when the node reports no runtime. */}\n {(() => {\n const rt = runtimeIdentity(session.runtime);\n if (!rt) return null;\n const br = isOnline ? 7 : 5.5;\n const bx = pos.x + radius * 0.72;\n const by = pos.y + radius * 0.72;\n const icon = br * 2 * 0.62;\n // Round 208 / Loop: runtime badge joins the micro-lift\n // radius-axis family. R177 grew the hub ring on hover\n // (r 14→17); R197 grew the legend swatch (r 5.5→7);\n // R208 closes the trio at per-node grain — the runtime\n // badge (CLI/SDK/HTTP indicator at avatar bottom-right)\n // pops r 7→8 (online) / 5.5→6.5 (offline) when the\n // parent node is hovered, with stroke 1.5→2 for\n // matching emphasis. R26 already lifts the label 1.5px\n // and R194 elevates its drop-shadow; R208 gives the\n // runtime indicator its own hover acknowledgement so\n // every per-node surface participates in the gesture.\n // CSS r-as-property + stroke-width are transitionable\n // (same support matrix R197/R198/R199 leveraged:\n // Chrome ≥95 / Safari ≥16 / FF ≥70). data-runtime-\n // badge-active exposes the gate for tests.\n const isNodeActive = !reducedMotion && hoveredAlias === session.alias;\n return (\n <g style={{ pointerEvents: 'none' }}>\n <circle\n cx={bx} cy={by} r={br}\n fill={pal.containerBg}\n stroke={rt.color}\n strokeWidth=\"1.5\"\n data-runtime-badge={session.alias}\n data-runtime-badge-active={isNodeActive ? 'true' : 'false'}\n style={{\n r: isNodeActive ? `${br + 1}px` : `${br}px`,\n strokeWidth: isNodeActive ? '2px' : '1.5px',\n transition: 'r 150ms ease-out, stroke-width 150ms ease-out',\n } as React.CSSProperties}\n />\n <g transform={`translate(${bx - icon / 2} ${by - icon / 2}) scale(${icon / 24})`}>\n <path d={rt.iconPath} fill=\"none\" stroke={rt.color} strokeWidth=\"2.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </g>\n </g>\n );\n })()}\n {/* Round 294 / Loop: per-node \"working\" pulse dot retired.\n The pulse was R24's per-node working indicator — a\n small green circle at the top of each working node,\n SMIL-animated opacity 1→0.25→1. After R278 retired the\n working halo, R279 retired arrival ping + dispatch\n pulse, R280 retired backdrop spokes, the pulse dot\n was the last surviving per-node SMIL animation in\n the original \"working = breathing\" visual family.\n Status info is preserved through 4 redundant signals:\n status ring green color (R167), label sub-text\n 'working' (R211), chip-row 'X working' count (top\n of canvas), hub centre digit (R130). With 30+\n working nodes on a real fleet, 30 simultaneous SMIL\n pulses add cognitive load with zero new information.\n Same R275-R281/R290/R291 减法 family idiom — the\n last 'wiggling per-node decoration' retires. Gated\n via `{false && ...}` per the R276/R278/R279/R280\n rollback-friendly pattern; the block stays in the\n file documented + dead-coded so future readers see\n the retired pulse-dot rationale + can A/B-restore\n it by flipping the gate. */}\n {false && (() => {\n const sse = sseCountFor ?? 0;\n const dur = sse >= 4 ? '0.7s' : sse >= 2 ? '0.9s' : '1.2s';\n const visible = session.status === 'working';\n return (\n <g\n data-pulse-wrapper={session.alias}\n data-pulse-visible={visible ? 'true' : 'false'}\n style={{\n opacity: visible ? 1 : 0,\n transition: 'opacity 300ms ease-out',\n pointerEvents: 'none',\n }}\n >\n <circle cx={pos.x} cy={pos.y - (radius - 6)} r=\"2.5\" fill={pal.flowParticle} data-pulse-dur={dur} opacity={reducedMotion ? 0.6 : undefined}>\n {!reducedMotion && (\n <animate attributeName=\"opacity\" values=\"1;0.25;1\" dur={dur} repeatCount=\"indefinite\" />\n )}\n </circle>\n </g>\n );\n })()}\n\n {/* Round 98 (issue #61): label rect 124px → 100px.\n Round 109/110 (Vincent P0): full opaque card below the\n density threshold / on hover / when zoomed; otherwise a\n lightweight plain-text alias that keeps every node\n labelled without an opaque box covering its neighbours.\n Round 15 / Loop: when nodeScale=S the node shrinks 30%\n but the label card was staying full-size, so the label\n visually outweighed the small node. Tighten the card\n frame, alias / sub fontSize, drop-offset and truncate\n length specifically for S; M and L keep their existing\n sizes (M ≈ L for labels by design — the S user is the\n one who explicitly asked for a denser view). */}\n {(() => {\n const isSmall = nodeScale < 0.8;\n const cardW = isSmall ? 88 : 100;\n const cardH = isSmall ? 36 : 42;\n const cardTopY = isSmall ? -12 : -14;\n const aliasFs = isSmall ? 11 : 12;\n const subFs = isSmall ? 8 : 9;\n const subY = isSmall ? 15 : 17;\n const dropY = isSmall ? 18 : 22;\n const fullMax = isSmall ? 11 : 12;\n const denseFs = isSmall ? 9 : 10;\n const denseDrop = isSmall ? 12 : 14;\n // Round 26 / Loop: micro-lift the label group on hover —\n // 1.5 px upward, 200 ms ease. Pairs with the Round 18\n // group-box hover-accent treatment to give the same\n // \"this is the focused element\" feedback at the per-\n // node level. CSS transform stacks onto the SVG\n // positioning transform attribute (SVG 2 cascade);\n // bbox is unchanged at rest, so the overlap-test gate\n // continues to see the geometric layout it expects.\n return showFullLabel ? (\n <g transform={`translate(${pos.x}, ${pos.y + radius + dropY})`} style={{ pointerEvents: 'none' }}\n className=\"transition-transform duration-200 group-hover:-translate-y-[1.5px]\">\n {/* Round 194 / Loop: label card picks up a subtle\n drop-shadow that intensifies when the node is\n hovered — pairs the existing R26 1.5px lift\n with physical weight. Pre-R194 the card lifted\n 1.5 px on hover but had no shadow follow, so\n the gesture read as \"card translated\" rather\n than \"card rose off the canvas\". Adding a\n baseline shadow at rest (1px/2px blur) plus a\n deeper hover state (3-4px/8-12px blur) makes\n the elevation feel earned.\n Same R57/R135 hover-elevation idiom that\n panels already use, now extended to per-node\n label cards. data-node-label-card-elevation\n ('idle'/'hover') exposes the state for tests.\n Filter is theme-aware (light: slate alpha;\n dark: black alpha) so the shadow stays visible\n against both surfaces. transition: filter\n 220ms ease-out matches R135's panel-elevation\n duration so a hovered node + a hovered panel\n fade at the same rhythm. Reduced-motion users\n collapse to the rest-state shadow only — no\n hover differentiation. Per-node filter is\n gated to showFullLabel which itself is gated\n to non-dense fleets (≤16 nodes) or hovered/\n zoomed-in branches, so the cost stays bounded\n (~20-30 cards max). */}\n {/* Round 217 / Loop: label card stroke tints to\n legendAccent (cyan) on parent-node hover,\n adding a 3rd hover-feedback channel alongside\n R26 1.5px lift + R194 drop-shadow elevation.\n The card now responds at three layers when its\n parent node is hovered: lift (motion) → shadow\n (depth) → stroke (color tint). All three are\n gated by the same hoveredAlias === session\n .alias state so they ease in unison. transition\n list extends `stroke 220ms ease-out` alongside\n R194's existing filter 220ms — single pair of\n eyes on the card reads \"this is the focused\n element\" via three independent channels. Pin\n + chat states don't compete: pinning a node\n opens chat (R136) but doesn't drive hovered\n Alias, so this stroke tint is exclusively a\n pointer-on-target signal. */}\n {/* Round 246 / Loop: label card chrome picks up\n fill + opacity in its transition list. R142\n already eased filter (drop-shadow) + stroke\n (R217 cyan tint on hover); the rect's fill\n (pal.labelBox.fill: cyber #020617 ↔ light\n #ffffff) and theme-derived opacity (0.94\n cyber / 1 light) still snapped on theme\n toggle. R211 already closed the alias/sub\n text-fill snap on the same card; R246\n closes the chrome-fill snap on the rect\n BEHIND that text, so the whole card\n (background + text) transitions as one\n unit through every theme switch. Same\n 220ms cadence the existing filter/stroke\n pair uses — coordinated 4-property easing\n across the card. */}\n <rect\n x={-cardW / 2} y={cardTopY} width={cardW} height={cardH} rx=\"6\"\n fill={pal.labelBox.fill}\n stroke={!reducedMotion && hoveredAlias === session.alias\n ? pal.legendAccent\n : pal.labelBox.stroke}\n opacity={isLight ? 1 : 0.94}\n data-node-label-card={session.alias}\n data-node-label-card-elevation={\n !reducedMotion && hoveredAlias === session.alias ? 'hover' : 'idle'\n }\n style={{\n filter: !reducedMotion && hoveredAlias === session.alias\n ? (isLight\n ? 'drop-shadow(0 3px 8px rgba(15,23,42,0.20))'\n : 'drop-shadow(0 4px 12px rgba(0,0,0,0.60))')\n : (isLight\n ? 'drop-shadow(0 1px 2px rgba(15,23,42,0.08))'\n : 'drop-shadow(0 1px 2px rgba(0,0,0,0.30))'),\n transition: 'filter 220ms ease-out, stroke 220ms ease-out, fill 220ms ease-out, opacity 220ms ease-out',\n }}\n />\n {/* Round 211 / Loop: alias + sub text fill eases on\n status flip, matching R167 status-ring fill 300ms.\n Pre-R211 a node going working → idle → offline made\n the ring smoothly recolor (R167) while the label\n card's text snap-cut to the new tier hue in a\n single frame — the node \"transitioned its ring,\n flipped its text\". 300ms inline transition syncs\n all four label-card fills (alias, sub, ring fill,\n ring stroke) to the same beat so the node reads\n as one coordinated status change.\n data-node-alias-text exposes the gate for tests. */}\n {/* Round 305 / Loop: node alias label text picks\n up the pin-signature letter-spacing family\n (R219 / R220) when the node is the chat\n target. The alias is the per-node identity\n label inside the label card; when chat is\n open targeting this node, R242 already adds\n a cyan-tint stroke to the card. R305 brings\n the alias text into the same pin-signature\n family — letter-spacing 0px → 0.5px when\n chatAlias === session.alias. Same vocabulary\n R219 established for recent-row text (line\n ~6354), legend-row text (~6881), and the\n R220 edge-badge text (~4327, with 0.4 for\n hot/pin). Now the per-node alias has its\n own pin signature when chat is open on it.\n transition list extends 'letter-spacing\n 200ms ease-out' so it eases alongside the\n existing 300ms fill transition. */}\n <text\n x=\"0\" y=\"1\" textAnchor=\"middle\"\n fill={status.text}\n fontSize={aliasFs} fontFamily=\"monospace\" fontWeight=\"700\"\n data-node-alias-text={session.alias}\n data-node-alias-chat-target={chatAlias === session.alias ? 'true' : 'false'}\n style={{\n transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out',\n letterSpacing: chatAlias === session.alias ? '0.5px' : '0px',\n }}\n >\n {truncate(session.alias, fullMax)}\n </text>\n <text\n x=\"0\" y={subY} textAnchor=\"middle\"\n fill={status.primary}\n fontSize={subFs} fontFamily=\"monospace\"\n data-node-sub-text={session.alias}\n style={{ transition: 'fill 300ms ease-out' }}\n >\n {status.label}{isOnline && sseCountFor != null ? ` sse:${sseCountFor}` : ''}\n </text>\n </g>\n ) : (\n // Round 212 / Loop: dense plain-text alias gets fill\n // transition on status flip — extension of R211. Pre-\n // R212 the dense fallback (denseLayout > 16 nodes,\n // where label cards collapse to plain text + R110\n // stroke halo) snap-cut its fill on tier change while\n // the status ring smoothly transitioned (R167) — the\n // card-mode equivalent that R211 just fixed at the\n // ≤16-node grain. Inline transition list combines\n // R26 transform 200ms (group-hover lift) + R212 fill\n // 300ms (status flip ease) — Tailwind transition-\n // transform on className would be displaced by inline\n // transition, so the transform property is explicit\n // in the inline list too. The group-hover:-translate-\n // y-[1.5px] className still fires the transform via\n // CSS pseudo-class; only the transition-property\n // moves to inline. Big fleets benefit most — this is\n // the path users see when their dashboard is busiest.\n <text\n x={pos.x}\n y={pos.y + radius + denseDrop}\n textAnchor=\"middle\"\n fill={status.text}\n fontSize={denseFs}\n fontFamily=\"monospace\"\n fontWeight=\"700\"\n opacity={0.9}\n className=\"group-hover:-translate-y-[1.5px]\"\n data-node-dense-alias-text={session.alias}\n style={{\n pointerEvents: 'none',\n paintOrder: 'stroke',\n transition: 'transform 200ms ease-out, fill 300ms ease-out',\n }}\n stroke={pal.containerBg}\n strokeWidth=\"3\"\n >\n {truncate(session.alias, isSmall ? 9 : 10)}\n </text>\n );\n })()}\n {/* v0.10.0 Hero 3 Wave 1 §3.E — hover detail card.\n Renders an extended-info SVG card next to the\n hovered node showing vendor / model / runtime /\n server fields that don't fit in the compact label\n card. Position flips to the left when the node is\n in the right half of the canvas so the card\n doesn't extend past the viewBox right edge. Only\n one card is visible at any time (gated on\n hoveredAlias === session.alias), so layout cost\n stays bounded.\n Reuses pal.labelBox.fill / pal.legendAccent for\n chrome consistency with the existing label card +\n legend panel. data-topo-hover-detail attribute\n exposes the element for test probes.\n Not rendered in dense layout (>16 nodes) — same\n gate as showFullLabel; dense fleets already have\n too much per-node chrome competing. */}\n {!reducedMotion && hoveredAlias === session.alias && !denseLayout && (() => {\n const v = vendorForModel(session.model);\n const rt = runtimeIdentity(session.runtime);\n const flipLeft = pos.x > VIEWBOX_W * 0.65;\n const detailW = 192;\n const detailH = 88;\n const detailX = flipLeft ? pos.x - radius - 18 - detailW : pos.x + radius + 18;\n const detailY = pos.y - detailH / 2;\n return (\n <g transform={`translate(${detailX}, ${detailY})`} data-topo-hover-detail={session.alias} style={{ pointerEvents: 'none' }}>\n <rect\n x=\"0\" y=\"0\" width={detailW} height={detailH} rx=\"8\"\n fill={pal.labelBox.fill}\n stroke={pal.legendAccent}\n opacity={isLight ? 0.98 : 0.94}\n style={{ filter: isLight ? 'drop-shadow(0 4px 12px rgba(15,23,42,0.16))' : 'drop-shadow(0 4px 12px rgba(0,0,0,0.6))' }}\n />\n <text x=\"10\" y=\"16\" fontSize=\"9\" fontFamily=\"monospace\" fill={pal.legendAccent} fontWeight=\"700\">\n {v.id !== 'unknown' ? v.label : '—'}\n </text>\n <text x=\"10\" y=\"32\" fontSize=\"10\" fontFamily=\"monospace\" fill={pal.legendHeadline}>\n {session.model || 'model · pending'}\n </text>\n <text x=\"10\" y=\"48\" fontSize=\"9\" fontFamily=\"monospace\" fill={pal.legendText}>\n {rt ? rt.label : 'runtime · pending'}\n </text>\n <text x=\"10\" y=\"64\" fontSize=\"9\" fontFamily=\"monospace\" fill={pal.legendText}>\n host · {session.server || 'unknown'}\n </text>\n <text x=\"10\" y=\"80\" fontSize=\"9\" fontFamily=\"monospace\" fill={pal.legendText} opacity=\"0.7\">\n {session.task ? truncate(session.task, 28) : 'no recent task'}\n </text>\n </g>\n );\n })()}\n </g>\n );\n })}\n\n {/* Round 14 / Loop: click ripple — one-shot expanding ring from\n the clicked node, ~500ms, status-coloured. Sits inside the\n zoom/pan <g> so it scales / pans with the topology. Keyed by\n ts so a re-click on any node (same or different) remounts the\n <circle> and the SMIL <animate> elements replay from t=0.\n strokeWidth=2 doesn't match the overlap-test selectors. */}\n {/* Round 227 / Loop: click-ripple SMIL gets ease-out curve.\n Pre-R227 both <animate>s ran with default calcMode=linear,\n which made the ripple grow at constant velocity from\n r0+4 → r0+30 over 500ms — a mechanical \"expansion at\n uniform rate\" feel that didn't match the rest of the\n topology's interaction vocabulary (every CSS transition\n on hover-lift, status-flip, pin-signature uses\n `ease-out`). On click, the ripple is the user's primary\n \"I did that\" confirmation feedback — it should feel\n fast-then-settle like a real pressure wave, not metric.\n\n calcMode=\"spline\" + keyTimes=\"0;1\" + keySplines=\"0.25 0.1\n 0.25 1\" maps directly onto CSS cubic-bezier(0.25, 0.1,\n 0.25, 1), the canonical ease-out curve. SMIL's keySplines\n uses the same 4 control-point convention as CSS but\n space-separated. Applied to BOTH the r and opacity\n <animate> elements so they ease in lockstep — the ring\n decelerates as it expands and fades, the two motions\n together feeling like one organic pulse.\n\n One change reaches three click surfaces — hub center\n (R52), node body (R14), edge midpoint badge (R185) — all\n reuse this single ripple element via the shared\n setClickRipple state. data-click-ripple attr surfaces\n the element for test introspection; calcMode attribute\n on the <animate> reflects the ease-out adoption. */}\n {clickRipple && !reducedMotion && (\n <circle\n key={clickRipple.ts}\n cx={clickRipple.x}\n cy={clickRipple.y}\n r={clickRipple.r0 + 4}\n fill=\"none\"\n stroke={clickRipple.color}\n strokeWidth=\"2\"\n opacity=\"0\"\n data-click-ripple\n style={{ pointerEvents: 'none' }}\n >\n <animate\n attributeName=\"r\"\n values={`${clickRipple.r0 + 4};${clickRipple.r0 + 30}`}\n dur=\"0.5s\"\n calcMode=\"spline\"\n keyTimes=\"0;1\"\n keySplines=\"0.25 0.1 0.25 1\"\n fill=\"freeze\"\n />\n <animate\n attributeName=\"opacity\"\n values=\"0.7;0\"\n dur=\"0.5s\"\n calcMode=\"spline\"\n keyTimes=\"0;1\"\n keySplines=\"0.25 0.1 0.25 1\"\n fill=\"freeze\"\n />\n </circle>\n )}\n\n </g>\n\n {/* #112: overlay panels (recent-signal + legend) render OUTSIDE the\n zoom/pan <g> so they stay fixed while the topology pans/zooms.\n They're sized + tucked into the top corners so every corner of\n each panel is >325px from the canvas centre — i.e. fully outside\n the outermost (offline) ring. No node on any ring can reach the\n corner triangles, so the panels never overlap a node, in ring\n OR grid layout (Vincent 4727 zero-overlap criterion). */}\n {/* latest flow labels */}\n {/* Round 57 / Loop: drop-shadow on the panel rects gives them\n card-like elevation, especially on light theme where the\n near-white fill on a near-white canvas read as pasted-on.\n data-topo-panel-elevation tag so the test can verify both\n panels carry the filter. The filter is on the rect, not\n the parent <g>, so it doesn't shadow the rows + text inside\n — only the panel chrome lifts.\n\n v0.10.0 Hero 3 Wave 1 / RFC §3.C (Vincent 5222 holdover):\n hide recent-signal panel when there's no flow to show.\n Pre-v0.10.0 the panel always-mounted with a \"no flow yet\n · send a message between agents\" placeholder. On a fresh\n fleet that's a full corner of chrome doing nothing —\n exactly the always-mount-stack 5222 calls out. Render the\n panel only when flowLinks actually has rows. R175 fade-in\n still applies — first flow that arrives still eases in.\n Composes with §3.I canvas-corner watermark (only shows\n when this panel is absent). */}\n {flowLinks.length > 0 && (\n <g\n transform=\"translate(16, 16)\"\n data-topo-panel=\"recent\"\n data-topo-panel-hovered={hoveredPanel === 'recent' ? 'true' : 'false'}\n // Round 175 / Loop: corner panels fade-in after the\n // R9/R72/R172/R173/R174 canvas content reveal. Pre-R175\n // recent-signal + legend panels appeared instantly in\n // the corners while nodes/edges/group boxes staggered\n // in around them — felt like 'panels are already\n // there, content shows up'. Delaying the panels to\n // ~700ms (after the first node wave finishes ~540ms\n // and edges begin filling in ~280ms) makes them drop\n // into place AFTER the canvas has revealed.\n // recent-signal panel at 700ms; legend at 800ms below\n // for a soft left-then-right cascade. .anet-fade-in\n // is the same R3 mount-once animation the other 4\n // wave layers use — fifth surface in the family.\n className=\"anet-fade-in\"\n data-topo-panel-fade-delay={700}\n style={{ animationDelay: '700ms' }}\n onMouseEnter={() => setHoveredPanel('recent')}\n onMouseLeave={() => setHoveredPanel(prev => prev === 'recent' ? null : prev)}\n >\n {/* Round 331 / Loop: recent-signal panel rect rx 8 → 10\n for proportional corner-radius rhythm after R330\n bumped the outer canvas wrapper to rounded-xl (12 px).\n Pre-R331 the panel sat at rx=8 (matching the legacy\n rounded-lg wrapper envelope); now it follows the\n wrapper one tier down:\n outer wrapper rounded-xl 12 px (R330)\n inner SVG panels rx=10 10 px (R331)\n inner detail card rx=8 8 px (codex 8f981a9)\n node label card rx=6 6 px (legacy R63)\n Geometry-safe: rx changes paint only, not bbox; the\n topo-overlap-test reads bbox geometry. Sibling change\n at the legend panel rect below (~line 6914) keeps\n the two corner panels symmetric. */}\n <rect\n x=\"0\" y=\"0\" width=\"230\" height=\"88\" rx=\"10\"\n fill={pal.legendBox.fill}\n stroke={pal.legendBox.stroke}\n opacity={isLight ? 0.97 : 0.92}\n style={{\n /* R135: drop-shadow intensifies on panel hover. Base\n shadow (2px / 6px blur) signals card elevation\n (R57); hovered (4px / 12px blur) tells the user\n the whole chrome is interactive territory — rows\n pin (R116), footer navigates (R133), legend rows\n pin status (R61). Reuses R18's KPI-card hover-\n elevation idiom for visual consistency. Theme-\n aware shadow colour stays the same; just the\n spread + blur grow.\n\n Round 247 / Loop: extend the transition list to\n include fill + stroke + opacity at 200ms. R135\n already eased filter (hover drop-shadow); the\n three theme-driven properties (pal.legendBox.fill\n cyber #020617 ↔ light #ffffff, pal.legendBox.\n stroke cyber #1f2937 ↔ light #e2e8f0, opacity\n 0.92 ↔ 0.97) still snapped on theme toggle. Same\n per-element 4-property easing R246 added to the\n per-node label card chrome — now applied at the\n panel scope so the whole panel (background + chrome\n + shadow) eases as one unit through theme switches. */\n filter: hoveredPanel === 'recent'\n ? (isLight ? 'drop-shadow(0 4px 12px rgba(15,23,42,0.14))'\n : 'drop-shadow(0 4px 12px rgba(0,0,0,0.65))')\n : (isLight ? 'drop-shadow(0 2px 6px rgba(15,23,42,0.08))'\n : 'drop-shadow(0 2px 6px rgba(0,0,0,0.45))'),\n transition: 'filter 200ms ease-out, fill 200ms ease-out, stroke 200ms ease-out, opacity 200ms ease-out',\n }}\n data-topo-panel-elevation=\"recent\"\n />\n {/* Round 266 / Loop: panel title fill picks up theme-toggle\n transition. Pre-R266 the title \"recent signal\" had\n fill={pal.legendHeadline} (cyber #e5e7eb ↔ light\n #0f172a) without any inline transition — so the BIGGEST\n text in the recent-signal panel (fontSize 12 fontWeight\n 700) hard-flipped color on theme toggle while the panel\n rect (R247) and every row inside (various) eased.\n Sibling treatment to the legend panel title at line\n ~6195 — the panel-pair's titles now ease together. */}\n {/* Round 301 / Loop: panel titles get letterSpacing=\"0.3\"\n for editorial parity with R289 watermark letterSpacing\n + R285 kicker tracking-widest. At fontSize 12 monospace\n fontWeight 700, default 0px letter-spacing reads as a\n code-style label; 0.3px gives it a touch of designed-\n header register without changing the lowercase\n terminal-style aesthetic. Sibling treatment applied\n to recent-signal panel title (here) and legend panel\n title (line ~6556) — both panels share the same\n editorial-text-spacing convention. data-recent-panel-\n title handle unchanged so R266 test still resolves. */}\n {/* Round 345 / Loop: recent-signal panel title gains\n letter-spacing tween 0.3 → 0.4 on panel hover.\n hoveredPanel === 'recent' is set by the panel <g>\n wrapper's onMouseEnter (line ~6263 area). Sibling to\n R344 hover-letter-spacing applied to the +N more\n flows footer — same gesture vocabulary at a panel-\n title scope: hovering the panel chrome spreads the\n title 0.1 px, signalling \"this is a coherent unit\n you're entering\". transition list extends letter-\n spacing 200ms ease-out alongside existing fill 200ms. */}\n <text x=\"13\" y=\"21\" fill={pal.legendHeadline} fontSize=\"12\" fontFamily=\"monospace\" fontWeight=\"700\" letterSpacing={hoveredPanel === 'recent' ? '0.4' : '0.3'} style={{ transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out' }} data-recent-panel-title>recent signal</text>\n {/* R96: header count now matches what the rows show. Pre-R96\n this read \"X msgs\" off the raw messages array, but the\n rows below render DEDUPED flowLinks — so a fleet with 10\n messages aggregating to 3 pairs read \"10 msgs\" above\n only 3 rows. Misreads as \"where are the other 7?\".\n \"X flows\" mirrors flowLinks.length one-for-one. When\n flows < msgs the chip-row's \"N active links · last 2s\"\n already tells the operator about traffic volume — no\n duplicate metric needed here.\n R129 / Loop: header gains an amber \" · N hot\" tail\n when ≥ 1 flowLink has count ≥ 10. The third surface\n of the hot-lane convention (R126 canvas badge / R127\n row count) lives at the panel header so a user\n scanning vertically — header → rows — gets a top-\n level summary before reading each row's amber digit.\n Restructured into a single <text> with <tspan>\n fragments so the amber portion can carry its own\n fill + weight without a sibling <text>. Switched\n anchor x=150 left-justified → x=217 right-justified\n so the count column unifies visually with the legend\n panel's right-justified header (line 3511 — also\n fontSize 10 monospace, also x≈215 textAnchor end).\n data-recent-panel-count stays on the flow tspan so\n the R96 / R128 tests still resolve. data-recent-\n panel-hot-count exposes the hot bucket count. */}\n {(() => {\n const hotFlowCount = flowLinks.filter(l => l.count >= 10).length;\n const hotStroke = isLight ? '#d97706' : '#fbbf24';\n // R162 / Loop: freshness tint on the panel-header\n // count tspan. R161 just colored the chip-row's \"N\n // active links · last 5s\" bullet by recency; the\n // recent-signal panel header is the panel-side\n // mirror of the same metric (flowLinks.length). Both\n // scopes now speak the same freshness vocabulary,\n // so a glance at either tells the operator whether\n // the network is firing right now. Four nested\n // scopes share one ladder:\n // canvas edge fade (R10)\n // row pip (R160)\n // chip bullet (R161)\n // panel header (R162, this round)\n // Same alpha ramp:\n // ageSec ≤ 30 → 1.0 (fully fresh)\n // 30-300s → smooth decay 1.0 → 0.25\n // > 300s → 0.25 stale floor\n // Hot tail (amber \" · N hot\" R129) is independent\n // of recency and keeps its own color — recency\n // tints the head; volume colors the tail.\n const recentMs = flowLinks.reduce<number | null>((acc, l) => {\n if (!l.last_at) return acc;\n const t = Date.parse(l.last_at);\n if (Number.isNaN(t)) return acc;\n return acc === null || t > acc ? t : acc;\n }, null);\n const ageSec = recentMs !== null\n ? Math.max(0, (Date.now() - recentMs) / 1000)\n : 999;\n const alpha = ageSec <= 30\n ? 1\n : ageSec <= 300\n ? 1 - ((ageSec - 30) / 270) * 0.75\n : 0.25;\n // Dark cyan-400 / light teal-600 with alpha — same\n // palette as R161's chip bullet so the two scopes\n // visually align even side-by-side.\n const freshFill = isLight\n ? `rgba(13, 148, 136, ${alpha.toFixed(2)})`\n : `rgba(34, 211, 238, ${alpha.toFixed(2)})`;\n return (\n <text\n x=\"217\" y=\"21\"\n textAnchor=\"end\"\n fontSize=\"10\"\n fontFamily=\"monospace\"\n >\n {/* Round 225 / Loop: tabular-nums on the panel-header\n flow-count tspan. The \"{N} flows\" string lives in\n a right-justified text anchor (x=217 textAnchor=\n 'end') so the BASELINE of the numeral is the same\n regardless of digit-count — but the SPACING between\n the digit and ' flows' label is monospace-jittery\n in the 1-digit → 2-digit boundary, and the ' · N\n hot' R190 tail that hangs off the end shifts by\n whatever the digit width delta is. Tabular-nums\n locks both, so the header reads stable through\n 9 flows → 10 flows growth. Sibling treatment to\n R224 edge badge / R225 hub digit. */}\n {/* Round 311 / Loop: recent-signal panel count tspan\n picks up fontWeight=600 for sibling parity with\n R310 legend panel count. Closes the panel-pair\n count typography symmetry — both top-corner\n panels now have:\n title fontWeight=700 (panel chrome anchor)\n count fontWeight=600 + tabular-nums (data)\n Same digit-semibold rule R309 established for\n per-row counts now applied to BOTH panel-summary\n counts. The R162 freshness fill (1.0→0.25 alpha\n ramp) and R225 tabular-nums all preserved; only\n the weight bumps. */}\n {/* Round 336 / Loop: split the digit from the unit\n word \" flows\" with a nested tspan at opacity=0.7.\n Same chip-internal-hierarchy pattern R333 (vendor\n count suffix) + R335 (filter pin prefix) applied\n to one chip — recurring \"small label spans demote,\n value stays prominent\" idiom at the panel-header\n count scope. The digit stays fw=600 + tabular-nums\n (R311 + R225 inheritance via the outer tspan);\n the unit tspan inherits fw=600 but adds opacity\n 0.7. Reads as \"5 (prominent) / flows (recessive\n unit)\". data-recent-panel-count attribute stays\n on the OUTER tspan so existing R311 fontWeight\n tests + count value reads still resolve via\n .textContent. data-recent-panel-count-unit on\n the inner unit tspan for R336 introspection. */}\n <tspan\n fill={freshFill}\n fontWeight=\"600\"\n data-recent-panel-count\n data-recent-panel-count-freshness-alpha={alpha.toFixed(2)}\n style={{\n transition: 'fill 200ms ease-out',\n fontVariantNumeric: 'tabular-nums',\n }}\n >{flowLinks.length}<tspan opacity=\"0.7\" data-recent-panel-count-unit> flows</tspan></tspan>\n {/* Round 190 / Loop: R129 hot-tail gets anet-fade-in\n for entrance. Pre-R190 the tspan snapped into\n the header the moment hotFlowCount crossed 0,\n and snapped out the moment it dropped back to 0.\n Same trade-off R67 accepts for filter pills:\n fade-IN smooth, accept exit snap. The CSS\n animation plays once when the tspan mounts\n (count goes 0 → 1+); subsequent re-renders\n (count growing from 1 → 2 → 3 hot flows)\n preserve the element via React reconciliation\n so the fade-in doesn't replay. Layout-shift\n cost is paid once on entrance — the parent\n <text textAnchor=\"end\"> recomputes its\n anchor as the tspan appears, then stays\n stable as the digit grows. Exit-snap is rare\n in steady operation: a hot flow cooling back\n below 10 messages doesn't happen often. */}\n {/* Round 223 / Loop: hot-tail tspan always-mounts;\n visibility crossfades via inline opacity gate\n instead of conditional mount/unmount on hot\n FlowCount > 0. Pre-R223 R190's anet-fade-in gave\n a smooth entrance but exit was snap (the family's\n original \"fade-IN smooth, accept exit snap\" trade-\n off). R223 closes the asymmetry — both directions\n now ease 300ms. anet-fade-in className kept for\n R190 test compat (plays once on initial render\n if the page loads with hotFlowCount > 0); subsequent\n threshold crossings use the opacity transition\n bi-directionally. Text content gates to empty\n string when hidden so the parent <text textAnchor=\n \"end\"> doesn't compute anchor against stale \"·\"\n separator. data-recent-panel-hot-visible exposes\n the gate for tests. Always-mount-opacity-gate\n family now hits 10 surfaces (R181/R182/R183/R213×2/\n R214/R215/R221/R222/R223). */}\n {/* Round 322 / Loop: hot count tspan picks up\n fontVariantNumeric tabular-nums for parity with\n its left-sibling tspan (R311 `{flowLinks.length}\n flows`, already tabular). Pre-R322 a hotFlowCount\n crossing 1→10 widened the leading digit and (since\n the parent <text> is textAnchor=\"end\") shifted the\n WHOLE header left a few pixels — visible micro-\n jitter against the panel rect's left edge. Tabular-\n nums locks the digit so the right-anchored block\n stays stable as hotFlowCount grows. 8th surface\n in the info-density tabular-nums sweep:\n R224 edge badge / R225 hub digit / R225 panel\n flows-count + recent-row count / R229 group-\n label count / R230 group-label status pips /\n R320 recent-row count fw=600 (left neighbour) /\n R321 recent-row timestamp / R322 panel hot\n count (this round). */}\n <tspan\n fill={hotStroke}\n fontWeight=\"700\"\n data-recent-panel-hot-count={hotFlowCount}\n data-recent-panel-hot-visible={hotFlowCount > 0 ? 'true' : 'false'}\n className=\"anet-fade-in\"\n opacity={hotFlowCount > 0 ? 1 : 0}\n style={{\n transition: 'opacity 300ms ease-out',\n fontVariantNumeric: 'tabular-nums',\n }}\n >\n {/* Round 336 / Loop: split hot count from \" hot\"\n unit word with nested opacity-0.7 tspan. Same\n chip-internal-hierarchy idiom this round\n applies to the \"{N} flows\" tspan above (R311\n sibling) — digit prominent at fw=700 + amber\n fill, unit recessive at 0.7 opacity. data-\n recent-panel-hot-count-unit exposes the unit\n tspan for R336 probes. */}\n {hotFlowCount > 0 ? (\n <>\n {` · ${hotFlowCount}`}\n <tspan opacity=\"0.7\" data-recent-panel-hot-count-unit> hot</tspan>\n </>\n ) : ''}\n </tspan>\n </text>\n );\n })()}\n {/* Round 45 / Loop: empty state. The panel used to render\n \"recent signal\" + \"0 msgs\" with three blank slots below\n when no flow yet — read as \"broken\" rather than \"quiet\".\n A muted centred placeholder makes the empty state\n deliberate. Messages count CAN diverge from flowLinks\n count (raw count vs. deduped pairs), so the placeholder\n fires on flowLinks.length=0 specifically. */}\n {/* Round 222 / Loop: empty state always-mounts; visibility\n crossfades via wrapper <g> opacity instead of conditional\n mount/unmount on flowLinks.length === 0. Pre-R222 the\n first flow arriving snap-removed the empty state in one\n frame while R203 rows simultaneously faded IN — half-\n smooth, half-snap on this \"first data\" first-impression\n moment. R222 closes the snap so empty fades OUT while\n rows fade IN — a proper crossfade for the user's most\n emotionally-loaded moment (the empty-to-populated flip).\n\n R200 SMIL breath on each text continues running\n regardless of parent opacity (SVG opacity is\n multiplicative — same R214 pulse-dot composition idiom).\n When parent opacity is 0, SMIL still animates child\n opacity but the result composes to invisible. CSS and\n SMIL don't fight, they layer.\n\n 300ms transition matches R203 row fade-in pace so the\n empty-fade-out + rows-fade-in pair share rhythm during\n the crossfade. */}\n <g\n data-recent-signal-empty-wrapper\n data-recent-signal-empty-visible={flowLinks.length === 0 ? 'true' : 'false'}\n style={{\n opacity: flowLinks.length === 0 ? 1 : 0,\n transition: 'opacity 300ms ease-out',\n pointerEvents: 'none',\n }}\n >\n {/* Round 258 / Loop: re-center the empty state within the\n post-R256 88-tall panel. R45 placed \"no flow yet\" at\n y=54 + the hint at y=68 inside an 84-tall panel — those\n baselines sat 12px / 26px below the panel mid-line at\n y=42, optically balanced for the original height. R256\n grew the panel 84 → 88 to give the \"+N more flows\"\n footer underline breathing room, shifting the panel\n mid-line to y=44 — but the empty state stayed put,\n drifting 2px high. R258 pushes both empty-state lines\n +2 (main y=54 → y=56, hint y=68 → y=70) so the pair\n sits 12px / 26px below the new mid-line, restoring\n the R45 optical balance. The R222 always-mount opacity\n gate is unaffected (geometry-only shift), and the\n R200 SMIL breath continues unchanged. Hint baseline\n y=70 still sits 12px above the footer baseline (y=82),\n same vertical rhythm as the row 3 → footer gap. */}\n {/* Round 302 / Loop: empty-state hint 'no flow yet' picks\n up letterSpacing='0.2' for editorial parity with R301\n panel titles (0.3) + R285 kicker tracking-widest +\n R289 watermark letterSpacing. The hint is the\n panel's quietest authored text (italic monospace\n fontSize 10 opacity 0.65); adding a small positive\n letter-spacing keeps it in the same designed-label\n family without lifting its visual weight. 0.2px is\n slightly less than the panel titles' 0.3 — appropriate\n for the smaller fontSize + lower opacity (empty-state\n is intentionally quieter than the header above it). */}\n <text\n x=\"115\" y=\"56\" textAnchor=\"middle\"\n fill={pal.legendText}\n fontSize=\"10\" fontFamily=\"monospace\" fontStyle=\"italic\"\n letterSpacing=\"0.2\"\n opacity={0.65}\n data-recent-signal-empty\n data-recent-signal-empty-breathes={reducedMotion ? 'false' : 'true'}\n >\n no flow yet\n {!reducedMotion && (\n <animate\n attributeName=\"opacity\"\n values=\"0.55;0.78;0.55\"\n dur=\"4.4s\"\n repeatCount=\"indefinite\"\n />\n )}\n </text>\n {/* Round 259 / Loop: instructional hint bumps fontSize 8 → 9\n for readability. Pre-R259 the empty-state hint was at\n the smallest readable size on the canvas (8pt), with\n italic + opacity 0.45 layering legibility cost on top\n — instructional text users need to READ to act on,\n yet eye-straining at default 1× zoom. 9pt italic stays\n visually subordinate to the 10pt main \"no flow yet\"\n AND to the 9pt regular row text (italic alone\n discriminates from row content) while easing the\n legibility floor. Sibling change at the +N-more\n footer link (line ~6047) applies the same bump to\n the panel's other italic secondary text. Per-row\n timestamp at y=38+i*16 (fontSize 8 right-edge\n recency tag) STAYS at 8 — it's an at-a-glance\n recency tag tightly co-located with row text, not\n read-to-act instruction. */}\n {/* Round 304 / Loop: secondary instructional hint\n 'send a message between agents' gets letterSpacing\n '0.15'. Extends the R301/R302 editorial-spacing\n family one layer down. The hint is the quietest\n authored text in the recent-signal panel (fontSize\n 9 italic-less opacity 0.45, sits below the R302\n main empty-state hint at fontSize 10 italic\n opacity 0.65). 0.15px is below R302's 0.2px to\n match the visual hierarchy: smaller + quieter\n text gets less letter-spacing.\n 5-axis editorial-letterspacing hierarchy now:\n R285 kicker: 1.2px (eyebrow loud)\n R289 watermark: 0.5px (wordmark brand)\n R301 panel titles: 0.3px (section headers)\n R302 empty main: 0.2px (empty-state hint)\n R304 empty hint: 0.15px (instructional sub)\n Each step ~0.1-0.5x scale-down matches the\n font-size + opacity descent. */}\n {/* Round 339 / Loop: empty-state sub-hint picks up\n fontStyle=\"italic\" for parity with the main hint\n above (line ~6526). Pre-R339 the main hint \"no\n flow yet\" was italic while the sub-hint \"send a\n message between agents\" was upright — two empty-\n state texts sharing the same quiet informational\n role but rendered in different styles. R339 closes\n the inconsistency: both texts now read as deliberate\n italic empty-state messaging. The R304 letter-\n spacing 0.15 + R259 fontSize 9 + opacity 0.45 +\n SMIL opacity breath all preserved. */}\n <text\n x=\"115\" y=\"70\" textAnchor=\"middle\"\n fill={pal.legendText}\n fontSize=\"9\" fontFamily=\"monospace\" fontStyle=\"italic\"\n letterSpacing=\"0.15\"\n opacity={0.45}\n data-recent-signal-empty-hint\n data-recent-signal-empty-hint-breathes={reducedMotion ? 'false' : 'true'}\n >\n send a message between agents\n {!reducedMotion && (\n <animate\n attributeName=\"opacity\"\n values=\"0.36;0.58;0.36\"\n dur=\"4.4s\"\n begin=\"-1.5s\"\n repeatCount=\"indefinite\"\n />\n )}\n </text>\n </g>\n {flowLinks.length === 0 ? null : (\n // Round 56 / Loop: each row is a navigator into the canvas.\n // Hover a row → set hoveredEdgeKey, which the existing R50\n // edge-focus + R49 endpoint-highlight ladders consume. The\n // matching flow edge brightens to 2× + thickens, its two\n // endpoint nodes stay full opacity, and every other edge +\n // non-endpoint node dims. Released → all restore. Wrapping\n // <g> + a transparent 218×14 hitbox so the cursor doesn't\n // have to land precisely on the truncated text.\n flowLinks.slice(0, 3).map((link, index) => {\n // Round 94 / Loop: per-row relative timestamp. The chip\n // row shows \"last 2s\" for the most recent flow overall,\n // but a user scanning the recent-signal panel had no way\n // to tell whether row 2 was 5s old or 5m old without\n // hovering nodes. Compact `2s` / `1m` glyph at the\n // right edge — same relativeAgo helper R42 uses for\n // the chip — pulls double duty as a recency anchor and\n // a sortedness hint (top row is freshest by construction).\n // Strip the \" ago\" suffix — at fontSize=8 in a 32-px\n // right-edge slot, every char counts. \"30s ago\" → \"30s\".\n const rawAt = link.last_at ? relativeAgo(link.last_at) : null;\n const lastAt = rawAt ? rawAt.replace(/\\s+ago$/, '') : null;\n const isRowHovered = hoveredEdgeKey === link.key;\n const isRowPinned = pinnedEdgeKey === link.key;\n const isRowActive = isRowHovered || isRowPinned;\n // Round 191 / Loop: timestamp text on row's right edge\n // picks up the R160 row-pip freshness ramp at a\n // different alpha range — gives the timestamp the same\n // visual recency encoding the pip has on the LEFT\n // edge, so both ends of the row mirror each other.\n // ≤30s → opacity 0.85 (fresh, high contrast)\n // 30-300s → 0.85 → 0.30 (smooth decay)\n // >300s → 0.30 (stale floor)\n // Pre-R191 the timestamp was static 0.55 — the same\n // visual weight whether the message just fired or\n // happened 5 minutes ago. Fill stays legendText gray\n // (not cyan) because the left pip already carries the\n // cyan; the right timestamp encodes recency through\n // contrast against the dark canvas, not hue.\n const tsAlpha = !link.last_at ? 0.55 : (() => {\n const ageSec = Math.max(0, (Date.now() - Date.parse(link.last_at)) / 1000);\n return ageSec <= 30 ? 0.85\n : ageSec <= 300 ? 0.85 - ((ageSec - 30) / 270) * 0.55\n : 0.30;\n })();\n // R127: panel-side mirror of R126's canvas hot-badge.\n // The recent-signal row text packs `alias→alias / N /\n // preview` into one line, with N rendered identically\n // regardless of magnitude. Now that the canvas badge\n // tells the user \"≥ 10 msgs = hot lane\" via amber\n // stroke, the panel row needs the same affordance so\n // the user reading the list at a glance can spot hot\n // lanes without crossing to the canvas. Renders the\n // count digit in amber + 700-weight when isHot; the\n // surrounding alias text + separators stay in the\n // existing legendText/legendHeadline palette. Reuses\n // R126's hotStroke colour for visual consistency.\n const isHot = link.count >= 10;\n const hotStroke = isLight ? '#d97706' : '#fbbf24';\n return (\n <g\n key={link.key}\n data-recent-row={link.key}\n data-recent-row-hovered={isRowHovered ? 'true' : 'false'}\n data-recent-row-pinned={isRowPinned ? 'true' : 'false'}\n data-recent-row-hot={isHot ? 'true' : 'false'}\n data-recent-row-lifted={(isRowHovered || isRowPinned) ? 'true' : 'false'}\n // Round 203 / Loop: per-row mount fade-in. R175 already\n // eased the whole panel in once, but new flows rising\n // INTO the top-3 list (or replacing an older row) snap-\n // popped in. React reconciliation via key={link.key}\n // preserves stable rows across re-renders, so anet-\n // fade-in only plays on mount — never replays when\n // counts update or rows reorder by recency. Stacks on\n // the panel's own R175 anet-fade-in: SVG opacity\n // composes multiplicatively, so during the first paint\n // the panel's 700ms delay holds rows hidden until the\n // panel reveals, then row opacity transitions inside\n // the visible panel. For mid-session arrivals (panel\n // already at opacity 1) the row's 150ms fade-in plays\n // standalone. Three layers of mount-once eases now\n // share rhythm: panel (R175) → rows (R203) → row\n // contents (existing R160 pip / R191 ts opacity\n // ramps animate independently after mount).\n className=\"anet-topo-svg-focus anet-fade-in\"\n role=\"button\"\n tabIndex={0}\n aria-pressed={isRowPinned}\n // R143 / Loop: extend the R135/R142 \"interactive surface\n // elevates\" idiom down one layer to the recent-signal\n // panel rows. R104 already tints the row background on\n // hover; R143 adds a 1-px translate so the row text\n // visually lifts off the panel — same vocabulary R51\n // uses for nodes, R135 uses for panels, R142 uses for\n // group boxes. Pinned rows lift too (sticky state\n // should look like locked-in selection). Reduced-motion\n // safe via prefers-reduced-motion blanket override\n // applied to transition-duration in globals.css.\n style={{\n cursor: 'pointer',\n transform: (isRowHovered || isRowPinned) ? 'translateY(-1px)' : undefined,\n transition: 'transform 150ms cubic-bezier(0.4, 0, 0.2, 1)',\n }}\n onPointerDown={(e) => e.stopPropagation()}\n onMouseEnter={() => setHoveredEdgeKey(link.key)}\n onMouseLeave={() => setHoveredEdgeKey(prev => prev === link.key ? null : prev)}\n // R116: click toggles pin. activeEdgeKey =\n // hoveredEdgeKey ?? pinnedEdgeKey so the matching\n // edge stays \"hot\" after mouseleave; click again\n // (or Esc) releases.\n onClick={() => setPinnedEdgeKey(prev => prev === link.key ? null : link.key)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setPinnedEdgeKey(prev => prev === link.key ? null : link.key);\n }\n }}\n >\n {/* R148 / Loop: row tooltip with full message context.\n The row text truncates aliases to 6 chars (R127)\n and content to 8 chars — useful for scan-density\n but obscures the underlying message. A native SVG\n <title> reveals the full alias / content /\n timestamp on hover. Pinned vs unpinned switches\n the click hint so the user knows the next\n gesture's effect. R98 enriched the node tooltip\n the same way for the source/destination scope;\n R148 brings the per-row equivalent to the panel\n side. */}\n <title>{[\n `${link.from} → ${link.to} · ${link.count} msg${link.count === 1 ? '' : 's'}${isHot ? ' (hot lane · ≥ 10)' : ''}`,\n link.last_at ? `last: ${new Date(link.last_at).toLocaleString()}` : null,\n link.content ? `\"${link.content}\"` : null,\n isRowPinned ? 'click to release pin (Esc to clear)' : 'click to pin · hover to preview',\n ].filter(Boolean).join('\\n')}</title>\n {/* R104: subtle row-background tint on hover. R56\n already brightens the matching edge on the canvas,\n but the panel row itself stayed flat — felt more\n like text-with-handlers than a navigable list.\n Filling the rect at hover with `pal.legendAccent`\n at low alpha gives the row visual feedback at the\n source surface, mirroring the list-item idiom from\n the chip-row pills. R116: pinned rows tint\n stronger than hovered ones so locked vs preview\n is discriminable. */}\n <rect\n x=\"6\" y={38 + index * 16 - 10}\n width=\"218\" height=\"14\" rx=\"3\"\n fill={isRowActive ? pal.legendAccent : 'transparent'}\n opacity={isRowPinned ? (isLight ? 0.18 : 0.22)\n : isRowHovered ? (isLight ? 0.10 : 0.14)\n : 1}\n style={{ transition: 'fill 150ms ease-out, opacity 150ms ease-out' }}\n />\n {/* Round 160 / Loop: recency pip. Canvas flow edges\n fade by freshness (R10: full intensity ≤30s →\n ~35% over 5min). The recent-signal panel rows\n duplicate that data (alias→alias · N · 5s)\n but encode freshness purely in text — no\n at-a-glance visual cue for \"which row is\n actively firing right now\". A 1.6-px cyan dot\n at x=10 (in the 7-px margin between rect-\n start x=6 and text-start x=13) brightens\n fresh rows and dims stale ones — same\n vocabulary the canvas uses, brought to the\n panel side.\n\n Three encodings now coexist on each row,\n none competing:\n rect fill = hover/pin state (R104/R116)\n count tspan = magnitude (R127 amber when ≥10)\n pip = recency (this round)\n\n Geometry: cy = row_y - 3 (mid-row vertical\n centre, where text baseline at y=row_y sits\n slightly below). r=1.6 fits cleanly in the\n 7-px left margin. pointerEvents:none so the\n row's button-role hit area is unchanged.\n No overlap-test impact (entirely within the\n existing rect bbox). */}\n {(() => {\n if (!link.last_at) return null;\n const ageSec = Math.max(0, (Date.now() - Date.parse(link.last_at)) / 1000);\n // 0-30s: fully fresh (1.0). 30-300s: smooth\n // decay 1→0.25. >300s: stale floor (0.25).\n const alpha = ageSec <= 30\n ? 1\n : ageSec <= 300\n ? 1 - ((ageSec - 30) / 270) * 0.75\n : 0.25;\n return (\n <circle\n cx={10}\n cy={38 + index * 16 - 3}\n r={1.6}\n fill={pal.legendAccent}\n opacity={alpha}\n data-recent-row-freshness={link.key}\n data-recent-row-freshness-alpha={alpha.toFixed(2)}\n style={{ pointerEvents: 'none', transition: 'opacity 200ms ease-out' }}\n />\n );\n })()}\n {/* Round 220 / Loop · milestone: recent-signal row\n text completes the pin-signature typography\n triple (R218 group labels / R219 legend rows /\n R220 recent-signal rows). All three label-based\n interactive surfaces now read \"locked in\" at\n the type level when pinned — letter-spacing\n spreads 0px → 0.5px on isRowPinned (NOT on\n hover — hover keeps default tracking so the\n eye can discriminate transient preview from\n sticky pin without checking chrome). Pin\n signature vocabulary now consistent across\n the entire interactive-label landscape of\n TopoGraph: every pin-able text element has\n a typography-level tell.\n transition extends 'letter-spacing 150ms'\n alongside R55 fill 150ms — same beat as\n R219 legend-row treatment. Hover still keeps\n its own R55 fill brighten exclusively;\n letter-spacing is pin-exclusive (note the\n isRowPinned not isRowActive gate). */}\n <text\n x=\"13\" y={38 + index * 16}\n fill={isRowActive ? pal.legendHeadline : pal.legendText}\n fontSize=\"9\"\n fontFamily=\"monospace\"\n data-recent-row-text={link.key}\n data-recent-row-text-pinned={isRowPinned ? 'true' : 'false'}\n style={{\n transition: 'fill 150ms ease-out, letter-spacing 150ms ease-out',\n letterSpacing: isRowPinned ? '0.5px' : '0px',\n }}\n >\n {/* R138 / Loop: typography unification with the rest\n of the topology UI. Filter pills (R119) render\n \"{from}→{to}\", node tooltips (R98) use →, the\n active-links chip tooltip (R114) and edge-badge\n titles all use unicode →. The recent-signal row\n was the lone holdout still rendering \"from -> to\"\n in ASCII. The data delimiter likewise: filter\n pills use \" · \" (\"status · 3\"); the row was using\n \" / \". Both swaps make the row read like every\n other surface — one less micro-style to remember. */}\n {truncate(link.from, 6)} {'→'} {truncate(link.to, 6)} {' · '}\n {/* Round 189 / Loop: count tspan unified — pre-R189\n two different tspans (data-recent-row-count vs\n data-recent-row-count-hot) mounted/unmounted\n on the R127 hot threshold (count >= 10),\n making fill (legendText ↔ amber) + fontWeight\n (regular ↔ 700) snap one-frame. Now one tspan\n always-mounted; isHot drives fill/fontWeight\n conditionally. style.transition='fill 300ms\n ease-out' makes the hot crossing ease through\n the colour shift — same vocabulary R188 just\n added to the edge midpoint badge stroke (the\n panel-side mirror of that surface). fontWeight\n stays binary (no clean weight interpolation\n across browsers). data-recent-row-count\n continues to expose the tspan to existing\n tests; data-recent-row-count-hot becomes\n an attribute on the same element when active\n so legacy probes still resolve. */}\n {/* Round 225 / Loop: tabular-nums on the per-row\n count digit. The row text reads \"alpha → beta ·\n {count} · content\"; when {count} grows from a\n single digit to two (9 → 10) the subsequent\n \" · {content}\" preview slides ~3-4px right in\n monospace because '1' and '0' have different\n natural widths against the surrounding control\n glyphs even in mono fonts. Tabular-nums locks\n the count column so the content preview\n column stays planted as activity scales up.\n Sibling treatment to R224 edge badge / R225\n hub digit / R225 panel-header flow-count. */}\n {/* Round 320 / Loop: cold-state per-row count gains\n explicit fontWeight=\"600\" instead of inheriting\n the parent <text>'s default (400). Brings the\n recent-signal row count into the 5-tier SVG\n data-weight family established by R309 (legend\n per-row count) / R310 (legend panel-header\n count) / R311 (recent-signal panel-header flow\n count). Pre-R320 the per-row count `· 12` for\n a cold row painted at fw=400, identical weight\n to the surrounding aliases — the count digit\n should read as data and stand out from the\n alias text. Hot crossing stays at fw=700 (R127),\n so cold→hot delta becomes 600→700 (still\n distinct, plus the fill flip from legendText\n → amber carries the dramatic part of the cue).\n Sibling treatment in the data-weight tier. */}\n <tspan\n fill={isHot ? hotStroke : undefined}\n fontWeight={isHot ? '700' : '600'}\n data-recent-row-count\n {...(isHot ? { 'data-recent-row-count-hot': 'true' } : {})}\n style={{\n transition: 'fill 300ms ease-out',\n fontVariantNumeric: 'tabular-nums',\n }}\n >\n {link.count}\n </tspan>\n {' · '}{truncate(link.content, 8)}\n </text>\n {lastAt ? (\n /* Round 321 / Loop: lastAt freshness timestamp picks\n up fontVariantNumeric tabular-nums. The string\n marches through 1s..59s (1 digit / 2 digits) /\n 1m..59m / 1h..24h every second the panel ticks,\n and the textAnchor=\"end\" right-aligns against\n x=217. Pre-R321 a 9s→10s crossing slid the chip\n left ~3px in monospace (digit '1' narrower than\n '0' even in mono) — same one-frame visible jitter\n R225 / R230 fixed elsewhere. Tabular-nums locks\n the digit slot so the timestamp stays planted as\n seconds tick. 7th surface in the info-density\n tabular-nums sweep after R224 edge badge / R225\n hub digit + panel header + recent row count /\n R229 group-label count / R230 group-label\n status pips / R320 recent-row count fw=600\n (count and timestamp now both lock). */\n <text\n x=\"217\" y={38 + index * 16}\n textAnchor=\"end\"\n fill={pal.legendText}\n fontSize=\"8\"\n fontFamily=\"monospace\"\n opacity={tsAlpha}\n data-recent-row-ts={link.key}\n data-recent-row-ts-alpha={tsAlpha.toFixed(2)}\n style={{\n pointerEvents: 'none',\n transition: 'opacity 200ms ease-out',\n fontVariantNumeric: 'tabular-nums',\n }}\n >\n {lastAt}\n </text>\n ) : null}\n </g>\n );\n })\n )}\n {/* Round 128 / Loop: overflow hint. The recent-signal panel\n renders the top 3 flowLinks via .slice(0, 3) — but a\n fleet with 5 or 10 active flows silently truncates the\n rest. The R96 \"X flows\" header tells the total but\n doesn't say \"you're seeing top-3\". This hint fires\n only when flowLinks.length > 3 so quiet fleets stay\n clean. Footer y=82 sits between row 3 (baseline 70)\n and the panel bottom; the R256 height bump 84→88\n adds 6 px of clear below the footer baseline so the\n on-hover textDecoration:underline (which renders\n ~3-4 px below baseline → y≈85-86) tucks INSIDE the\n panel border instead of clipping past it at the\n old 84-px floor. Overlap-test geometry unchanged\n (panel selector at translate(16,16); corner-to-\n center distance 342.6 → 340 still > 325 ring-clear\n threshold). fontStyle=italic + opacity 0.55 reads\n as muted metadata, not an actionable row — matches\n the R110 empty-state hint idiom. */}\n {(() => {\n // Round 221 / Loop: footer always-mounts; visibility\n // crossfades via wrapper <g> opacity instead of React\n // conditional mount/unmount on flowLinks.length > 3.\n // Pre-R221 a fleet's 4th flow appearing snap-popped the\n // footer in; tapering back to 3 flows snap-removed it.\n // Same threshold-crossing snap R215 closed for edge\n // midpoint badges, now applied to the recent-panel\n // footer surface. moreCount clamps at 0 so when invisible\n // (length ≤ 3) the data attribute and text don't show\n // garbage negative numbers. a11y trio (role / tabIndex /\n // aria-hidden) and pointerEvents follow visibility so\n // the hidden footer doesn't appear in tab order or\n // intercept clicks at its midpoint coordinates.\n const visible = flowLinks.length > 3;\n const moreCount = Math.max(0, flowLinks.length - 3);\n const label = `+ ${moreCount} more flow${moreCount === 1 ? '' : 's'}`;\n // R133: the truncation hint becomes a clickable nav to\n // /messages. R128 introduced the footer as pure metadata\n // (\"you're seeing top-3\"); R133 closes the gap by giving\n // users a way to ACT on that info — see the full list.\n // Wrap in <g> with onClick so SVG hit-testing fires the\n // route push. cursor:pointer + the underline-on-hover\n // visual cue tells users this is interactive. The hover\n // state is React-controlled (no CSS :hover on SVG <g>\n // descendant text would feel reliable across Chrome's\n // SVG quirks). pointerEvents follows visibility (R215\n // pattern) so the hidden footer can't intercept clicks\n // at its midpoint when it's invisible (sub-threshold\n // fleets).\n return (\n <g\n data-recent-panel-more-nav\n data-recent-panel-more-visible={visible ? 'true' : 'false'}\n role={visible ? 'link' : undefined}\n tabIndex={visible ? 0 : -1}\n aria-hidden={visible ? undefined : true}\n className=\"anet-topo-svg-focus\"\n style={{\n cursor: visible ? 'pointer' : undefined,\n pointerEvents: visible ? 'all' : 'none',\n opacity: visible ? 1 : 0,\n transition: 'opacity 300ms ease-out',\n }}\n onPointerDown={(e) => e.stopPropagation()}\n onMouseEnter={() => { if (visible) setHoveredRecentMore(true); }}\n onMouseLeave={() => setHoveredRecentMore(false)}\n onClick={() => { if (visible) router.push('/messages'); }}\n onKeyDown={(e) => {\n if (!visible) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n router.push('/messages');\n }\n }}\n >\n <title>{`${label} — open /messages for the full list`}</title>\n {/* Round 195 / Loop: footer hint adopts the cyan\n vocabulary on hover, joining the interactive-on-\n hover-is-cyan family (R178 fullscreen / R163\n layout / R179 nodeSize / R193 active-links chip).\n Pre-R195 the footer brightened on hover via\n opacity + underline but kept its gray fill —\n 'becomes brighter gray' rather than 'becomes the\n go-act color'. legendAccent is the same cyan-400\n (dark) / teal-600 (light) every other interactive\n hover speaks. transition list extends `fill 200ms\n ease-out` so the colour swap eases instead of\n snapping. data-recent-panel-more-hovered exposes\n the gate for tests. */}\n {/* Round 259 / Loop: footer link bumps fontSize 8 → 9\n for clickable-text readability. Pre-R259 the\n \"+N more flows\" footer (the panel's primary\n navigation affordance into /messages) sat at the\n same 8pt as the empty-state hint — small enough\n to make the click target feel cheap. 9pt + italic\n + opacity 0.55 keeps it visually secondary to row\n content (9pt regular) while giving the link\n enough type-weight to read as a real affordance.\n Underline geometry verified: with fontSize 9 the\n underline still renders ~3-4px below baseline at\n y=82 → underline ~y=85-86, panel bottom y=88 →\n 2-3px clear (R256 footer-breath invariant\n preserved). */}\n {/* Round 325 / Loop: footer link joins the editorial\n letter-spacing family at 0.2px — same axis as\n R302 empty-state main hint (fontSize 10 opacity\n 0.65). The footer is italic monospace fontSize 9\n opacity 0.55 acting as the panel's primary\n navigation affordance into /messages; pre-R325\n it sat orphaned from the R285/R289/R301/R302/R304\n editorial-spacing axis even though it carried\n the same designed-label semantics (action label,\n not row data). 0.2px is the same value as R302\n because at fontSize 9 vs 10 the visual density\n of \"+ N more flows\" is close to \"no flow yet\"\n and they're both italic — sibling-equal in the\n hierarchy. The R195 cyan-hover + R259 fontSize\n bump stay; this round only adds the spacing.\n 6-axis editorial-letterspacing hierarchy now:\n R285 kicker: 1.2px (eyebrow loud)\n R289 watermark: 0.5px (wordmark brand)\n R301 panel titles: 0.3px (section headers)\n R302 empty main: 0.2px (empty-state hint)\n R325 footer link: 0.2px (panel nav action) ← NEW\n R304 empty sub: 0.15px (instructional sub) */}\n {/* Round 340 / Loop: +N more flows footer link extends\n the R333/R335/R336/R337/R338 chip-internal-hierarchy\n arc to a 6th surface. The digit `{moreCount}` reads\n as the primary data (\"how many more flows\"); the\n unit text ` more flow(s)` recedes via a nested\n tspan with opacity-0.7 (multiplicative against the\n parent <text>'s hover/rest opacity, so unit always\n sits below the digit). The `label` variable is\n preserved for the <title> tooltip — only the SVG\n render splits. data-recent-panel-more-unit exposes\n the unit tspan for tests. */}\n {/* Round 344 / Loop: footer hover gains letter-spacing\n tween 0.2 → 0.3. R325 set rest letter-spacing\n 0.2 to join the editorial-spacing family; R344\n adds a 0.1px hover spread that layers on top of\n R195 cyan fill + R325 spacing + R133 underline\n so the footer reads \"lit up and spaced\" on\n hover — sibling to R218/R219/R220 pin-signature\n letter-spacing family applied to a hover-only\n surface. transition list extends letter-spacing\n 200ms ease-out alongside the existing opacity/\n fill easings. */}\n <text\n x=\"115\" y=\"82\"\n textAnchor=\"middle\"\n fill={hoveredRecentMore ? pal.legendAccent : pal.legendText}\n fontSize=\"9\"\n fontFamily=\"monospace\"\n fontStyle=\"italic\"\n letterSpacing={hoveredRecentMore ? '0.3' : '0.2'}\n opacity={hoveredRecentMore ? 0.85 : 0.55}\n textDecoration={hoveredRecentMore ? 'underline' : 'none'}\n data-recent-panel-more={moreCount}\n data-recent-panel-more-hovered={hoveredRecentMore ? 'true' : 'false'}\n style={{ transition: 'opacity 150ms ease-out, fill 200ms ease-out, letter-spacing 200ms ease-out' }}\n >\n {`+ ${moreCount}`}\n <tspan opacity=\"0.7\" data-recent-panel-more-unit>{` more flow${moreCount === 1 ? '' : 's'}`}</tspan>\n </text>\n </g>\n );\n })()}\n </g>\n )}\n\n {/* legend — Round 55 / Loop: each status row is now a hover\n target. Pointer enter sets `hoveredStatus`; pointer leave\n clears it. Node opacity formula composes the match below.\n The row text brightens to legendHeadline while hovered as\n a small affordance hint. Geometry unchanged — the new\n <g> wrappers only carry pointer handlers. */}\n <g\n transform=\"translate(760, 16)\"\n data-topo-panel=\"legend\"\n data-topo-panel-hovered={hoveredPanel === 'legend' ? 'true' : 'false'}\n // R175 / Loop: legend panel offset 100ms behind the\n // recent-signal panel so the two corner panels cascade\n // left-then-right rather than appearing in lockstep.\n // Same .anet-fade-in mechanism the four wave layers use.\n className=\"anet-fade-in\"\n data-topo-panel-fade-delay={800}\n style={{ animationDelay: '800ms' }}\n onMouseEnter={() => setHoveredPanel('legend')}\n onMouseLeave={() => setHoveredPanel(prev => prev === 'legend' ? null : prev)}\n >\n {/* R57: matching drop-shadow elevation to the legend panel.\n R106: panel height grew 96 → 104 to seat the new header\n line + 4 px row-shift below it (so the new header text\n doesn't overlap the row-1 hitbox region).\n R135: hover-elevation mirrors the recent-signal panel\n rect at line ~3299. Both panels grow their shadow on\n hover to telegraph \"the chrome is interactive\" since\n their rows pin / nav. */}\n {/* Round 247 / Loop: sibling treatment to the recent-signal\n panel — fill + stroke + opacity transitions added so\n the legend panel also eases through theme toggles\n (no snap on cyber↔light switch). Same 200ms cadence\n across the panel pair. */}\n {/* Round 331 / Loop: legend panel rect rx 8 → 10 — sibling\n treatment to the recent-signal panel above. Same\n proportional-rhythm step under R330's rounded-xl\n canvas wrapper envelope. */}\n <rect\n x=\"0\" y=\"0\" width=\"224\" height=\"88\" rx=\"10\"\n fill={pal.legendBox.fill}\n stroke={pal.legendBox.stroke}\n opacity={isLight ? 0.97 : 0.92}\n style={{\n filter: hoveredPanel === 'legend'\n ? (isLight ? 'drop-shadow(0 4px 12px rgba(15,23,42,0.14))'\n : 'drop-shadow(0 4px 12px rgba(0,0,0,0.65))')\n : (isLight ? 'drop-shadow(0 2px 6px rgba(15,23,42,0.08))'\n : 'drop-shadow(0 2px 6px rgba(0,0,0,0.45))'),\n transition: 'filter 200ms ease-out, fill 200ms ease-out, stroke 200ms ease-out, opacity 200ms ease-out',\n }}\n data-topo-panel-elevation=\"legend\"\n />\n {/* R106 / Loop: panel header — symmetric with the recent-\n signal panel's \"recent signal · N flows\" (R96). Same\n font + position vocabulary so the two side panels feel\n paired. Title text at x=13 y=21; total fleet count\n right-aligned at x=215 y=21 in the accent colour. */}\n {/* Round 266 / Loop: legend panel title fill picks up theme-\n toggle transition — sibling treatment to the recent-\n signal panel title at line ~5459. Pre-R266 both panel\n titles hard-flipped color on theme toggle while their\n surrounding chrome eased; R266 closes both at once. */}\n {/* R301: sibling to recent-signal panel title above —\n same letterSpacing 0.3 for editorial parity. */}\n {/* R345 sibling — legend panel title same hover letter-\n spacing tween 0.3 → 0.4 on panel hover. */}\n <text x=\"13\" y=\"21\" fill={pal.legendHeadline} fontSize=\"12\" fontFamily=\"monospace\" fontWeight=\"700\" letterSpacing={hoveredPanel === 'legend' ? '0.4' : '0.3'} style={{ transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out' }} data-legend-panel-title>legend</text>\n {/* Round 257 / Loop: legend panel header count picks up the\n symmetric 13L/13R inner-padding pattern from the recent-\n signal panel. Pre-R257 the legend header was 13px from\n the left edge (`x=13` title) but only 9px from the right\n edge (`x=215` end-anchored count → 224-215=9), while the\n recent-signal panel header used 13px on BOTH sides (x=13\n title + x=217 end-count → 230-217=13). The two panels\n sit as a side-by-side corner pair — mismatched header\n inner-padding read as a typographic nit on the panel\n chrome. x=211 (= 224-13) restores symmetric 13L/13R so\n the panel pair shares one inset rhythm. The per-row\n count text at x=215 (line ~6321) STAYS at 9px-from-right\n — that one is paired with the flow-arrow swatch geometry\n ('M140,80 Q164,56 196,80') and would visibly tighten\n against the arrow tip if moved further left. Header\n count has no such pairing; it stands alone. */}\n {/* Round 266 / Loop: legend count fill (pal.legendAccent\n — cyber #67e8f9 cyan ↔ light #10b981 emerald) picks up\n theme-toggle transition. Pre-R266 the count snapped\n color on theme flip; R266 eases it alongside the panel\n title (sibling text in the same header band). */}\n {/* Round 292 / Loop: legend panel header count adopts explicit\n fontVariantNumeric: 'tabular-nums' for parity with the\n recent-signal panel header count at line ~5814 (R232).\n The text is already fontFamily='monospace' so digit width\n is technically tabular by definition — the explicit\n directive documents intent at code level, survives a\n future font-family change without silently losing\n tabular alignment, and eliminates an asymmetry between\n two sibling panel-header counts. Sibling treatment to\n R225 (hub digit) / R224 (edge badge) / R232 (chip-row\n counts) — tabular-nums sweep continues wherever digits\n live next to non-digit characters. */}\n {/* Round 310 / Loop: legend panel-header count picks up\n fontWeight=600 for parity with R309 per-row count\n weight. Pre-R310 the header count 'N nodes' rendered\n at default 400 while the per-row counts (working\n 'N' / idle 'N' / offline 'N') went semibold in R309.\n Same hierarchy reason as R309: the count is the DATA\n operators scan; the label ('legend' panel title +\n row labels 'working/idle/offline') is stable\n structural anchor. R309 established the rule at the\n row scope; R310 propagates it up to the panel-\n summary scope so the count typography is consistent\n across both the rollup and per-row counts inside\n the same legend panel. Existing pal.legendAccent\n fill + tabular-nums + R266 fill transition all\n preserved. */}\n {/* Round 336 / Loop: split legend panel count digit from\n unit \" nodes\" with nested opacity-0.7 tspan — sibling\n treatment to the recent-signal panel count and hot-\n count splits above. Three-panel-header surface family\n now sharing the same chip-internal-hierarchy pattern:\n recent flows count + \" flows\" unit at 0.7\n recent hot count + \" hot\" unit at 0.7\n legend nodes count + \" nodes\" unit at 0.7\n data-legend-panel-count-unit on the inner tspan for\n R336 introspection; the parent .textContent still\n reads \"{N} node(s)\" so existing R310 count tests via\n textContent unchanged. */}\n <text\n x=\"211\" y=\"21\" textAnchor=\"end\"\n fill={pal.legendAccent} fontSize=\"10\" fontFamily=\"monospace\" fontWeight=\"600\"\n data-legend-panel-count\n style={{\n transition: 'fill 200ms ease-out',\n fontVariantNumeric: 'tabular-nums',\n }}\n >{sessions.length}<tspan opacity=\"0.7\" data-legend-panel-count-unit> node{sessions.length === 1 ? '' : 's'}</tspan></text>\n {(() => {\n const idleCount = onlineNodes.length - workingCount;\n // R106: rows shift +8 px (was y0=24, 48, 72 → 32, 56, 80)\n // to clear the new header row. R57 panel rect grew 96 →\n // 104 to seat them.\n const rows = [\n /* Round 277 / Loop: legend panel compress 104 → 88 (matches\n recent-signal panel height post-R256) per Vincent\n 5214/5215-5217 simplification ask. Row stride drops\n 24 → 18: row 1 working anchored at y0=32 (unchanged so\n R271 hitbox-swatch-center test at y=21 still passes);\n row 2 idle y0=56→50 (-6); row 3 offline y0=80→68 (-12).\n Flow-arrow swatch path (line ~6607) tracks new offline\n cy from y=80 to y=68. Net: legend panel takes ~15%\n less vertical chrome, panel pair (recent-signal+\n legend) now share same height = symmetric corner\n pair. Tests still pass: R257/R266/R269/R274 probe\n x attrs, fill transitions, text content — none\n sensitive to y0 stride. R271 probes working row\n hitbox y=21 (row.y0-11 with row.y0=32), unchanged.\n Corner-to-center distance increases (panel ends\n higher, further from center) — geometric ring-clear\n improves slightly. */\n /* Round 308 / Loop: continue the R307 legend label 减法.\n 'working node' (12 chars) → 'working' (7 chars):\n the 'node' qualifier is redundant — the row is in\n the LEGEND for a node graph, every row inherently\n describes a node state. 'online idle' (11 chars) →\n 'idle' (4 chars): the 'online' qualifier was\n disambiguation against the offline row, but the\n dashed-vs-solid status ring already discriminates\n online idle from offline visually. After R307+R308\n the three legend labels are all just status words:\n working / idle / offline — a clean 3-state list at\n roughly comparable lengths (7 / 4 / 7) for the\n tightest legend column to date. Pure 减法; visual\n information already encoded by row position +\n status-color swatch + status-ring dashing. */\n { key: 'working' as const, y0: 32, y1: 36, fill: isLight ? '#059669' : '#22c55e', label: 'working', count: workingCount },\n { key: 'idle' as const, y0: 50, y1: 54, fill: isLight ? '#0d9488' : '#2dd4bf', label: 'idle', count: idleCount },\n /* Round 269 / Loop: \" / \" → \" · \" delimiter unification.\n R138 swept the recent-signal row separators from\n ASCII \" / \" to typographic \" · \" (matching filter\n pills, node tooltips, edge badges, active-links\n tooltip). The legend's offline-row label was the\n LAST hardcoded \" / \" holdover in TopoGraph. Same\n monospace cell width (no layout shift), completes\n the R138 delimiter sweep.\n Round 307 / Loop: drop the ' · no SSE' qualifier.\n 'offline · no SSE' (16 chars) → 'offline' (7 chars).\n The visual already communicates the same idea\n redundantly: status ring strokeDasharray='5 5' for\n offline nodes (line ~5193) + gray fill + offline\n row's own gray swatch. Text qualifier was\n technical disambiguation that the visual encodes\n directly. Same R275-R281/R290/R291/R294 减法\n register — remove redundant text the eye doesn't\n need. Sibling row labels 'working node' (12 chars)\n + 'online idle' (11 chars) read at roughly the\n same length now too — legend rows look more\n balanced across the 3 lines. */\n { key: 'offline' as const, y0: 68, y1: 72, fill: isLight ? '#94a3b8' : '#6b7280', label: 'offline', count: offlineNodes.length },\n ];\n return rows;\n })().map(row => {\n // Round 61 / Loop: legend rows pin too — symmetric with the\n // R60 pressure-bar segments. R55 hover stays transient; the\n // new onClick toggles pinnedStatus so users can lock a\n // filter without holding the cursor still. Pinned row gets\n // an inset ring on the swatch (same vocab as R60).\n const isPinned = pinnedStatus === row.key;\n const isRowHovered = hoveredStatus === row.key;\n const isLifted = isRowHovered || isPinned;\n return (\n <g\n key={row.key}\n data-legend-status={row.key}\n data-legend-row-lifted={isLifted ? 'true' : 'false'}\n className=\"anet-topo-svg-focus\"\n role=\"button\"\n tabIndex={0}\n aria-pressed={isPinned}\n // R144: mirror of R143 — extend the row-lift idiom to the\n // legend panel rows for symmetry with the recent-signal\n // panel rows. Same translate-1px transform, same 150ms\n // cubic-bezier timing. R55 hovers the row (transient),\n // R61 pins it (sticky); both states earn the lift.\n // Composes with R105 row-bg-tint and R135 panel hover-\n // shadow → three layers of feedback at three nested\n // scopes (panel chrome → row text lift → bg tint).\n style={{\n cursor: 'pointer',\n transform: isLifted ? 'translateY(-1px)' : undefined,\n transition: 'transform 150ms cubic-bezier(0.4, 0, 0.2, 1)',\n }}\n // R61: stopPropagation on pointerdown so the SVG-level\n // pan handler (R103) doesn't setPointerCapture and\n // redirect the follow-up click away from this <g>.\n // Same trick the node <g> uses (and R52 hub).\n onPointerDown={(e) => e.stopPropagation()}\n onMouseEnter={() => setHoveredStatus(row.key)}\n onMouseLeave={() => setHoveredStatus(prev => prev === row.key ? null : prev)}\n onClick={() => setPinnedStatus(prev => prev === row.key ? null : row.key)}\n >\n {/* R149 / Loop: legend row gains a <title> tooltip\n symmetric with R148's recent-signal row tooltip.\n Same R97 idiom — anywhere the UI shows \"N\" should\n hover-explain WHICH N — applied to the legend\n panel which had been showing only the bucket\n count without alias context. Header line names\n the status bucket; body lists the matched aliases\n with 8-truncate + \"+N more\"; footer hint flips\n with pin state. Hot-lane convention doesn't apply\n here (status buckets aren't traffic counts), so\n no isHot suffix. */}\n <title>{(() => {\n const aliases = row.key === 'working'\n ? onlineNodes.filter(s => s.status === 'working').map(s => s.alias)\n : row.key === 'idle'\n ? onlineNodes.filter(s => s.status !== 'working').map(s => s.alias)\n : offlineNodes.map(s => s.alias);\n const preview = aliases.slice(0, 8).join(', ');\n const suffix = aliases.length > 8 ? ` + ${aliases.length - 8} more` : '';\n return [\n `${row.label} · ${row.count}`,\n aliases.length > 0 ? `${preview}${suffix}` : null,\n isPinned ? 'click to release pin (Esc to clear)' : 'click to pin · hover to preview',\n ].filter(Boolean).join('\\n');\n })()}</title>\n {/* R55 hitbox covers the row so cursor doesn't need to\n be exactly on the 5-px swatch. R105 / Loop: the\n hitbox now also carries a subtle hover/pin tint —\n mirroring R104's recent-signal row treatment so\n both side panels share the list-item idiom. The\n tint borrows the ROW'S OWN swatch colour rather\n than legendAccent, so the user's eye associates\n the tinted row with its colour swatch instead of\n a generic accent. Pin is a stronger tint than\n hover; idle stays fully transparent. */}\n {/* Round 271 / Loop: hitbox y shifts row.y0-12 → row.y0-11\n so hitbox center aligns exactly with swatch cy=row.y0.\n Pre-R271 hitbox spanned y=row.y0-12 to y=row.y0+10\n (center at row.y0-1), with swatch cy at row.y0 — 1px\n asymmetric. Hover/pin tint band drifted 1px above\n the swatch. Post-R271: hitbox spans y=row.y0-11 to\n y=row.y0+11 (center exactly at row.y0), swatch sits\n 11px from both edges (symmetric). Label text\n vertical center also benefits — label baseline\n y=row.y1=row.y0+4, visual midpoint ~row.y0+1.25,\n now ~1.25px below hitbox center (vs ~2.25px pre).\n No height change, no test ripple (other than this\n one), no R260/R268/R270 chrome regressions. */}\n <rect\n x=\"6\" y={row.y0 - 11}\n width=\"170\" height=\"22\" rx=\"3\"\n fill={hoveredStatus === row.key || isPinned ? row.fill : 'transparent'}\n opacity={isPinned ? (isLight ? 0.14 : 0.18)\n : hoveredStatus === row.key ? (isLight ? 0.08 : 0.12)\n : 1}\n data-legend-row-tinted={isPinned ? 'pinned' : hoveredStatus === row.key ? 'hover' : 'none'}\n style={{ transition: 'fill 150ms ease-out, opacity 150ms ease-out' }}\n />\n {/* Round 197 / Loop: swatch dot scales r 5.5 → 7 when its\n row is hovered or pinned. Pre-R197 the swatch was a\n flat constant size — the row got R55/R61 fill brighten,\n R105 bg tint, R144 1px row lift, R181 pin ring, but\n the swatch dot at the row's *visual identity anchor*\n stayed still. Adding the size response gives the\n swatch its own click/hover feel and visually rhymes\n with R177 hub hover-lift (hub ring r 14→17). Stays\n well inside the r=8 R181 pin ring (7 + 0 stroke vs\n 8 - 0.75 inner ≈ 7.25) so the two layers don't fight.\n CSS r-as-property is interpolatable by Chrome/Safari/\n FF post-2020. Geometry-unchanged at rest, so the\n overlap test sees the same baseline. */}\n {/* Round 295 / Loop: legend swatch base radius 5.5 → 6.\n Pre-R295 the swatch idle radius was 5.5 and R197\n grew it to 7 on hover/pin — a 1.5px jump (~27%\n area). Bumping idle to 6 keeps hover at 7, so\n the hover delta becomes a smoother 1px (~36%\n area but 17% radius). Side effect: idle swatch\n reads slightly more like an authored color\n anchor than a faint dot — matches the post-R294\n 减法 register where the legend is one of the\n few remaining persistent canvas-side info\n surfaces. Geometry stays well inside the r=8\n R181 pin ring (6 + 0 stroke vs 8 - 0.75 inner\n ≈ 7.25). data-legend-swatch is unchanged so\n R197 / R55 / R61 tests probe the same handle. */}\n <circle\n cx=\"16\" cy={row.y0}\n r=\"6\"\n fill={row.fill}\n data-legend-swatch={row.key}\n data-legend-swatch-state={isPinned ? 'pinned' : isRowHovered ? 'hover' : 'idle'}\n style={{\n r: isRowHovered || isPinned ? '7px' : '6px',\n transition: 'r 150ms ease-out',\n } as React.CSSProperties}\n />\n {/* R61 pinned-state ring — concentric stroke at r=8 in\n the row colour, draws OUTSIDE the swatch so it\n doesn't fight the fill colour the user is matching.\n Round 181 / Loop: the ring used to mount/unmount\n with the conditional render, snapping on every\n pin/unpin. Now always mounted with opacity gated\n by isPinned + a 150ms transition so the ring\n eases in on pin and out on unpin — same gesture\n vocabulary the R165/R180 smooth-pin-mirror family\n uses for the chip-row pin chips. strokeWidth=1.5\n is the R51 overlap-test sentinel but the test\n selector is gated to g[data-node] ancestors,\n so this legend-internal circle is invisible to\n that probe. pointerEvents:none so the ring can't\n intercept the row click that produced it. */}\n <circle\n cx=\"16\" cy={row.y0} r=\"8\"\n fill=\"none\"\n stroke={row.fill}\n strokeWidth=\"1.5\"\n opacity={isPinned ? 1 : 0}\n data-legend-pin-ring={row.key}\n data-legend-pin-ring-pinned={isPinned ? 'true' : 'false'}\n style={{\n pointerEvents: 'none',\n transition: 'opacity 150ms ease-out',\n }}\n />\n {/* Round 219 / Loop: legend row text gains the same\n letter-spacing pin signature R218 added to group\n labels — 0px → 0.5px when isPinned. Pre-R219 the\n legend row's hover and pin states shared fill\n colour (R55/R61 both brighten to legendHeadline)\n so the text was typographically identical at the\n letter-form level for transient hover vs sticky\n pin. R181 pin ring + R197 swatch grow + R143 row\n lift differentiated the row chrome; R219 adds the\n text-level signature so the LABEL itself reads\n \"locked in\" at type. Mirror of R218's group label\n treatment — chrome-level + type-level pin\n vocabulary now unified across both interactive\n label surfaces (group + legend). transition adds\n `letter-spacing 150ms` alongside R55 fill 150ms;\n same ease pace, same beat. */}\n <text\n x=\"30\" y={row.y1}\n fill={hoveredStatus === row.key || isPinned ? pal.legendHeadline : pal.legendText}\n fontSize=\"11\"\n fontFamily=\"monospace\"\n data-legend-row-label={row.key}\n data-legend-row-label-pinned={isPinned ? 'true' : 'false'}\n style={{\n transition: 'fill 150ms ease-out, letter-spacing 150ms ease-out',\n letterSpacing: isPinned ? '0.5px' : '0px',\n }}\n >{row.label}</text>\n {/* R95: live count anchored to the right edge of the\n panel (x=215, after the flow-arrow swatch). Same\n counts the chip-row shows (\"3 working\" etc.) but\n here next to the swatch the user is matching —\n saves crossing the canvas to the chip row for\n the number. text-anchor=end aligns the column\n visually like a table. pointerEvents:none so the\n count doesn't intercept the row hover hitbox.\n\n Round 204 / Loop: count text recedes when the\n tier is empty. Pre-R204 the \"0\" sat at the same\n opacity 0.65 as \"12\" — visually identical, so\n the eye got zero signal that a status tier was\n empty unless the operator read the digit. R204\n drops empty rows to 0.30 (dark) / 0.28 (light)\n so empty tiers fade into the panel chrome while\n populated tiers stay visually prominent. R204 a\n crossing zero / coming back from zero eases via\n the existing 150ms opacity transition. data-\n legend-count-empty exposes the binary signal\n for tests. */}\n {/* Round 239 / Loop: legend count text gains a tier-\n coloured fill on hover/pin, completing the hover-\n deepen-own-hue idiom at this surface. Pre-R239 the\n count digit's opacity bumped 0.65→0.95 on hover\n (R204 thinning) but its fill stayed at the neutral\n pal.legendText gray — same digit, brighter gray,\n no tier identity. R239 flips fill to row.fill\n (green/teal/slate per tier) when the row is\n hovered OR pinned, so the count lights up in its\n OWN colour, matching the swatch directly above it.\n The whole row now reads as one tier-coloured unit\n under cursor (swatch + label + count); R55/R197\n already do this for swatch + label, R239 closes\n the trio at the count. Opacity transition stays at\n 150ms; fill joins the same transition list at 150ms\n so the colour shift eases alongside the opacity\n ramp. data-legend-count-fill exposes the active\n fill state for tests; empty tiers (row.count===0)\n stay at pal.legendText regardless — empty doesn't\n get to claim tier identity. 8th surface in the\n hover-deepen-own-hue family. */}\n {/* Round 274 / Loop: legend per-row count picks up\n tabular-nums (sibling treatment to R225's recent-\n signal panel header flow-count + R230's group-\n label pip strip). The text uses fontFamily=\n 'monospace' which is typically tabular by\n nature, but some monospace implementations have\n subtle digit-pair width variance (e.g., '0' vs\n '1' at the visual boundary). Explicit\n fontVariantNumeric: 'tabular-nums' is belt-and-\n suspenders: locks digit widths regardless of\n the rendered monospace font, so the count\n column stays planted as offline/idle/working\n counters roll across 9→10 / 99→100 thresholds.\n Pure CSS-level addition, no layout shift.\n 10th surface in the info-density tabular-nums\n sweep family. */}\n {/* Round 309 / Loop: legend per-row count gains\n fontWeight=600 (semibold). The count is the\n DATA the operator scans (how many working /\n idle / offline nodes); the row label is the\n stable status word. Default weight (400) on\n both makes them visually equal — but a glance-\n first read pattern needs the digit to register\n faster than the label.\n fontWeight=600 gives the digit semibold\n emphasis (matching the h2 'Command mesh'\n semibold in the title block) while the row\n label stays at default 400/normal — a clean\n 'digit semibold > label regular' hierarchy\n that makes the count the optical anchor in\n each row. After R307+R308 simplified the\n labels to single status words, the count\n becomes proportionally more important; this\n round emphasizes that role typographically. */}\n <text\n x=\"215\" y={row.y1}\n textAnchor=\"end\"\n fill={row.count > 0 && (hoveredStatus === row.key || isPinned) ? row.fill : pal.legendText}\n fontSize=\"11\"\n fontFamily=\"monospace\"\n fontWeight=\"600\"\n opacity={row.count === 0\n ? (isLight ? 0.28 : 0.30)\n : (hoveredStatus === row.key || isPinned ? 0.95 : 0.65)}\n data-legend-count={row.key}\n data-legend-count-empty={row.count === 0 ? 'true' : 'false'}\n data-legend-count-fill={row.count > 0 && (hoveredStatus === row.key || isPinned) ? 'tier' : 'neutral'}\n style={{ pointerEvents: 'none', transition: 'opacity 150ms ease-out, fill 150ms ease-out', fontVariantNumeric: 'tabular-nums' }}\n >{row.count}</text>\n </g>\n );\n })}\n {/* Flow-arrow swatch tracks the offline row — R106 shifted\n rows down by 8 px to make space for the panel header so\n this moves from y=72 to y=80. Drop its pointerEvents so\n the offline legend row stays hoverable (R55). It's\n decoration, no need to receive events. */}\n {/* Round 254 / Loop: legend flow-arrow swatch stroke\n transition for theme toggle (cyber #67e8f9 ↔ light\n #10b981). Last theme-driven legend element snap. */}\n {/* Round 277 / Loop: flow-arrow path tracks new offline-row\n cy=68 after the legend panel compress (was 80 pre-R277).\n Endpoints follow the offline row to keep the swatch\n logically tied to the row it demonstrates; control point\n proportionally shifts so apex stays mid-arc between\n rows 2 and 3. */}\n <path d=\"M140,68 Q164,44 196,68\" fill=\"none\" stroke={pal.flowEdge} strokeWidth=\"3\" markerEnd=\"url(#topo-arrow)\" data-legend-flow-arrow style={{ pointerEvents: 'none', transition: 'stroke 200ms ease-out' }} />\n </g>\n\n {/* Round 282 / Loop: sleep2agi brand watermark per Vincent\n 5215 ask (relayed via 通信龙). Plain monospace text at\n canvas bottom-left (the only fully-empty corner — top\n corners hold recent-signal + legend panels, bottom-\n right holds the chrome strip). No icon yet — public/\n has only favicon.svg (small abstract network icon\n with hardcoded #0a0a1a dark bg that wouldn't blend on\n light theme) + intern_avatar.png (书生 brand-specific).\n Without a sleep2agi-specific crescent/lockup asset,\n R282 ships a low-opacity text-only mark; R283+ can\n swap in the real logo if Vincent provides the asset.\n\n Position: x=16 (matches the 16-unit SVG inset that the\n corner panels use); y=672 (≈12 px from viewBox bottom\n y=680, descender ≈ y=675, so the entire glyph sits\n clear of the bottom edge). Theme-aware fill:\n pal.legendText (cyber #94a3b8 slate-400 ↔ light\n #475569 slate-600). 0.4 opacity makes it a\n watermark — present but not visually loud. Pointer-\n events:none so it can't intercept clicks on the\n canvas backdrop.\n\n Note: the brand mark is INTENTIONALLY in a corner\n that no overlay/panel occupies, AND it's purely\n decorative additive after 7 rounds of 减法 (R275-\n R281). Adds 1 small text element back into the\n canvas — but Vincent specifically asked for it. */}\n {/* Round 289 / Loop: brand watermark picks up letterSpacing\n 0.5px. For a 9-character wordmark at fontSize 11 monospace,\n 0.5px between characters (8 gaps × 0.5 = 4px total\n widening) lifts \"sleep2agi\" from \"body text that happens\n to be a name\" to \"deliberate wordmark register\". Same\n R285-family idiom (kicker tracking-widest, title\n tracking-tight) applied to the brand mark — letter-\n spacing as typographic intent. Stays well inside the\n bottom-left corner; opacity 0.4 unchanged so the\n watermark stays a watermark. */}\n <text\n x=\"16\" y=\"672\"\n fontSize=\"11\" fontFamily=\"monospace\" fontWeight=\"600\"\n letterSpacing=\"0.5\"\n fill={pal.legendText}\n opacity=\"0.4\"\n data-topo-brand-watermark\n style={{ pointerEvents: 'none', transition: 'fill 200ms ease-out' }}\n >sleep2agi</text>\n {/* v0.10.0 Hero 3 Wave 1 / RFC §3.I (Vincent 5215 + 通信龙\n lead-autonomy Q4 dual-anchor minimal): canvas top-left\n crescent moon brand mark, visible ONLY when the\n recent-signal panel is hidden (composes with §3.C). The\n two never co-exist — when flowLinks.length > 0 the\n recent-signal panel occupies the (16,16) corner; when\n flowLinks.length === 0 the corner is empty and the\n brand crescent fills it. R310 title-block crescent\n remains the primary mark; this one is the secondary\n canvas-internal anchor (Q4 dual-anchor minimal).\n Inline path geometry identical to public/sleep2agi-\n logo.svg + the title-block SVG (mask = outer disc minus\n offset inner disc → crescent). Local mask id\n (`s2a-canvas-corner-mask`) prevents collision with the\n other inline crescents. opacity 0.35 (slightly more\n subtle than the bottom watermark's 0.4 since the\n canvas top-left has more contrast headroom). */}\n {/* Round 327 / Loop: canvas brand crescent joins the always-\n mount-opacity-gate family (R181/R182/R183/R213×2/R214/R215/\n R221/R222/R223). Pre-R327 the crescent conditionally\n mounted on `flowLinks.length === 0` — first flow arriving\n SNAP-removed it, last flow leaving SNAP-added it. The\n recent-signal panel at the same (16,16) corner has the\n same snap problem on its conditional-mount path; this\n round closes the crescent's snap-on-mount (the panel's\n own crossfade is a larger surface, deferred).\n\n Always-mounted with `opacity={flowLinks.length === 0 ?\n 0.35 : 0}` + 300ms ease-out transition: when the panel\n hides, the crescent fades in over 300ms; when the panel\n shows, the crescent fades out. Same opacity ramp time\n the R175 panel-fade-in uses for cascade rhythm. data-\n topo-brand-canvas-mark-visible exposes the gate for\n tests. */}\n <g\n opacity={flowLinks.length === 0 ? 0.35 : 0}\n data-topo-brand-canvas-mark\n data-topo-brand-canvas-mark-visible={flowLinks.length === 0 ? 'true' : 'false'}\n style={{ pointerEvents: 'none', transition: 'opacity 300ms ease-out, fill 200ms ease-out' }}\n >\n <defs>\n <mask id=\"s2a-canvas-corner-mask\">\n <rect x=\"0\" y=\"0\" width=\"28\" height=\"28\" fill=\"black\" />\n <circle cx=\"14\" cy=\"14\" r=\"12\" fill=\"white\" />\n <circle cx=\"17.5\" cy=\"13\" r=\"10\" fill=\"black\" />\n </mask>\n </defs>\n <rect\n x=\"16\" y=\"16\" width=\"28\" height=\"28\"\n fill={pal.legendText}\n mask=\"url(#s2a-canvas-corner-mask)\"\n />\n </g>\n </svg>\n\n {/* Round 30 / Loop: minimap. Big fleets in fullscreen mode at high\n zoom let users lose their position — the minimap shows the\n whole topology miniaturised plus a viewport rectangle so the\n user always knows where they are. Click anywhere to recenter\n the canvas there. Only mounted when the view is non-default\n (zoomed or panned) since at 1× centered the minimap and the\n canvas show the same thing. HTML overlay so it stays fixed\n while the SVG transforms. */}\n {(() => {\n const isDefaultView = Math.abs(view.zoom - 1) < 0.01 && Math.abs(view.x) < 1 && Math.abs(view.y) < 1;\n if (isDefaultView || (onlineNodes.length + offlineNodes.length) === 0) return null;\n const MW = 120, MH = 82;\n const sx = MW / VIEWBOX_W, sy = MH / VIEWBOX_H;\n const rectX = (-view.x / view.zoom) * sx;\n const rectY = (-view.y / view.zoom) * sy;\n const rectW = (VIEWBOX_W / view.zoom) * sx;\n const rectH = (VIEWBOX_H / view.zoom) * sy;\n return (\n <div\n /* Round 332 / Loop: minimap container rounded-md → rounded-lg\n (6 → 8 px) — continues the R330-R331 corner-radius cascade\n onto the minimap overlay card. The minimap is a smaller\n surface than the inner SVG panels (120×82 vs 230×88), so\n it sits one tier inward in the size hierarchy: panels at\n rx=10 (R331), minimap at rounded-lg=8 (R332), inner\n detail card at rx=8 (codex 8f981a9). Same 2 px gradient\n step the rest of the cascade uses. Geometry-safe — the\n minimap is an HTML overlay positioned `bottom: 56` +\n `right-4`, no impact on SVG layout or topo-overlap-test. */\n className=\"absolute right-4 rounded-lg border shadow-lg shadow-black/30 overflow-hidden anet-fade-in anet-topo-chip-focus\"\n /* Round 254 / Loop: minimap container theme transitions —\n background-color, border-color, color (used for SVG\n currentColor inside) all ease at 200ms alongside the\n R254 wrapper + R247 panel treatments. */\n style={{ bottom: 56, background: pal.legendBox.fill, borderColor: pal.containerBorder, cursor: 'crosshair', color: pal.legendAccent, transition: 'background-color 200ms ease-out, border-color 200ms ease-out, color 200ms ease-out' }}\n // R157: minimap a11y completion. Pre-R157 the element had\n // role=\"img\" + aria-label but no tabIndex / onKeyDown — it\n // was clickable for mouse users (recenter to where you\n // clicked) but tab-unreachable. role=\"img\" was also wrong\n // for an interactive surface; role=\"button\" matches the\n // canonical pattern R116 / R139 / R140 / R151 / R152 use.\n // Keyboard activation can't compute a click position, so\n // Enter / Space falls back to resetView() — same gesture\n // as the dedicated reset button (R104). Click semantics\n // unchanged; only added a clarifying tail to the aria-\n // label + title. anet-topo-chip-focus picks up R155's\n // cyan outline via color: pal.legendAccent inline so the\n // currentColor inherits cleanly on the rounded card.\n role=\"button\"\n tabIndex={0}\n aria-label=\"Topology minimap — click to recenter, Enter to reset view\"\n title=\"Minimap · click to recenter · Enter to reset view\"\n onClick={(e) => {\n const r = e.currentTarget.getBoundingClientRect();\n const fx = (e.clientX - r.left) / r.width;\n const fy = (e.clientY - r.top) / r.height;\n setView(prev => ({\n ...prev,\n x: VIEWBOX_W / 2 - fx * VIEWBOX_W * prev.zoom,\n y: VIEWBOX_H / 2 - fy * VIEWBOX_H * prev.zoom,\n }));\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n resetView();\n }\n }}\n // R346: viewport rect hover affordance driven by parent.\n onMouseEnter={() => setHoveredMinimap(true)}\n onMouseLeave={() => setHoveredMinimap(false)}\n onFocus={() => setHoveredMinimap(true)}\n onBlur={() => setHoveredMinimap(false)}\n data-topo-minimap\n data-topo-minimap-hovered={hoveredMinimap ? 'true' : 'false'}\n >\n <svg width={MW} height={MH} viewBox={`0 0 ${MW} ${MH}`} style={{ display: 'block' }}>\n {/* Round 198 / Loop: minimap dots gain smooth status\n transitions. Pre-R198 a session flipping working→idle\n or going offline made the minimap dot snap in a single\n frame (opacity 0.9→0.5, r 1.7→1.2, fill swap). The\n canvas itself eases all of these via R167 status-ring\n transitions, R10 freshness, R3 fade-in — but the\n minimap mirror was still snap-cut. Adding opacity /\n fill / r to the CSS transition list lets a status\n change ripple smoothly through both views at the\n same rhythm. 200ms matches the R167 nodeStrokeWidth\n interpolation on the main canvas so the two surfaces\n visually flip in sync. r-as-property is well supported\n Chrome ≥ 95 / Safari ≥ 16 / FF ≥ 70 (same support\n matrix R197 just leveraged on the legend swatch).\n data-topo-minimap-dot exposes each dot for the test;\n data-topo-minimap-dot-online encodes the binary status\n used by the visible attributes. */}\n {[...onlineNodes, ...offlineNodes].map(s => {\n const p = nodePositions[s.alias];\n if (!p) return null;\n const sseN = (s.network_id ? sseSessions[`${s.network_id}:${s.alias}`] : undefined) ?? sseSessions[s.alias];\n const isOn = s.status !== 'offline' || !!sseN;\n const st = nodeStatus(s, isOn, isLight);\n return (\n <circle\n key={s.alias}\n cx={p.x * sx} cy={p.y * sy}\n r={isOn ? 1.7 : 1.2}\n fill={st.primary}\n opacity={isOn ? 0.9 : 0.5}\n data-topo-minimap-dot={s.alias}\n data-topo-minimap-dot-online={isOn ? 'true' : 'false'}\n style={{\n transition: 'opacity 200ms ease-out, fill 200ms ease-out, r 200ms ease-out',\n } as React.CSSProperties}\n />\n );\n })}\n {/* viewport rectangle.\n Round 199 / Loop: rect dimensions transition smoothly\n during R169 smoothView arming (discrete zoom button\n clicks + keyboard +/− + reset/fit). Pre-R199 the main\n canvas crossfaded over R168's 280ms opacity blend\n while the minimap viewport rect snap-cut to its new\n x/y/w/h in one frame — exactly the same rhythm\n mismatch R198 just closed for the minimap dots, now\n fixed for the rectangle that frames them.\n\n Gated to smoothView=true so continuous wheel-zoom /\n drag-pan stay snappy (a CSS transition during drag\n would cause the rect to chase the cursor with a\n 280ms lag). Discrete zooms arm smoothView for 350ms,\n long enough to cover the 280ms x/y/w/h transition.\n\n Setting x/y/width/height as CSS PROPERTIES (style.x\n etc.) — same approach R197 used for legend swatch\n r. Modern Chrome/Safari/FF interpolate these.\n\n data-topo-minimap-viewport / -smooth expose state for\n tests. */}\n {/* Round 287 / Loop: minimap viewport rect strokeWidth\n 1 → 1.5. The rect frames the user's current view\n within the full topology — it IS the wayfinding\n indicator. At 1px stroke against a 120×82 mini-\n canvas it was readable but reserved; 1.5px gives\n the boundary clearer presence without crowding the\n miniaturised dots (still r 1.2-1.7) inside.\n Same micro-polish family as R283 monogram stroke\n 1 → 1.5 — small visual-weight bump on a high-\n information element to lift it above ambient\n chrome. opacity 0.9 stays — strokeWidth alone\n does the lifting. */}\n <rect\n x={Math.max(0, rectX)} y={Math.max(0, rectY)}\n width={Math.max(0, Math.min(MW - Math.max(0, rectX), rectW))}\n height={Math.max(0, Math.min(MH - Math.max(0, rectY), rectH))}\n fill=\"none\" stroke={pal.legendAccent}\n // R346: strokeWidth + opacity tween on container hover.\n strokeWidth={hoveredMinimap ? '1.75' : '1.5'}\n opacity={hoveredMinimap ? '1' : '0.9'}\n data-topo-minimap-viewport\n data-topo-minimap-viewport-smooth={smoothView ? 'true' : 'false'}\n data-topo-minimap-viewport-hover={hoveredMinimap ? 'true' : 'false'}\n style={{\n transition: smoothView\n ? 'x 280ms ease-out, y 280ms ease-out, width 280ms ease-out, height 280ms ease-out, stroke-width 200ms ease-out, opacity 200ms ease-out'\n : 'stroke-width 200ms ease-out, opacity 200ms ease-out',\n } as React.CSSProperties}\n />\n </svg>\n </div>\n );\n })()}\n {/* Round 103 (issue #81): zoom / pan / fullscreen controls — HTML\n overlay so they stay fixed while the SVG content transforms.\n Round 104: Vincent 实测 — the reset action used to be hidden\n behind the \"%\" label (looked like an indicator, not a button).\n Split into a plain % readout + an explicit reset button with\n its own icon + tooltip. */}\n {/* Round 261 / Loop: chrome strip bottom-3 right-3 (12 CSS px) →\n bottom-4 right-4 (16 CSS px) to align HTML overlay padding\n with the SVG corner panels at (16, 16) panel-translate. Pre-\n R261 the SVG panels (at 16 SVG units from canvas edges,\n ≈ 15 CSS px after render-scale ~0.94) and the HTML chrome\n (at 12 CSS px) sat at visually different distances from\n the canvas edges — small but real ~3 CSS px optical\n asymmetry between SVG-layer and HTML-layer overlay padding.\n 16 CSS px ≈ 17 SVG units, unifying the visual padding\n vocabulary across both layers. Sibling change at the\n minimap container (line ~6444, `absolute right-3` →\n `right-4`) keeps the bottom-right corner HTML overlays\n aligned at the same canvas-edge inset. */}\n {/* Round 326 / Loop: chrome strip outer wrapper gap 1.5 → 2\n (6px → 8px between control groups). Pre-R326 the four\n chrome groups (nodeSize segmented S/M/L, zoom +/100%/−,\n reset, fullscreen) sat 6px apart — close enough that on a\n busy canvas with bright cyan accents they read as one\n uniform strip rather than four distinct affordances. Bump\n to 8px gives each group its own visual breath without\n disturbing the bottom-4 right-4 corner-inset alignment.\n Sibling treatment to R298/R299 title-block gap polish on\n the top side of the canvas — both ends of the canvas\n chrome now breathe at the same 8px rhythm. Geometry-safe\n for the overlap-test (chrome is HTML overlay on top of\n the SVG, not part of the viewBox 1000x680 surface; ring\n r=325 / grid gx0 layout untouched). */}\n <div className=\"absolute bottom-4 right-4 flex items-center gap-2 text-xs select-none\" data-topo-chrome>\n {/* #113: node size — S / M / L segmented control (Vincent 4727).\n R154: stable data-* hooks for tests + focus-visible ring so\n keyboard navigation lands somewhere visible against the\n dark canvas (browser default outline often vanishes on\n cyber theme). */}\n {/* Round 264 / Loop: nodeSize wrapper gains theme-toggle\n transition. Pre-R264 the wrapper's bg (pal.legendBox.fill)\n + borderColor (pal.containerBorder) were inline theme-\n conditional, but neither inline transition nor a\n transition-colors className → wrapper snapped on cyber↔\n light flip while the inner S/M/L buttons eased via their\n own transition-colors. Same R254 holdover pattern that\n R263 just closed at the canvas wrapper scope, now at the\n chrome strip's nodeSize sub-wrapper scope. */}\n <div\n className=\"flex items-center rounded-md border overflow-hidden\"\n style={{\n background: pal.legendBox.fill,\n borderColor: pal.containerBorder,\n transition: 'background-color 200ms ease-out, border-color 200ms ease-out',\n }}\n role=\"group\"\n aria-label=\"Node size\"\n data-topo-chrome-fleet-group-trailer\n >\n {([['S', 0.7], ['M', 0.84], ['L', 1]] as const).map(([lbl, v], idx) => {\n const popKey = `size-${lbl}` as 'size-S' | 'size-M' | 'size-L';\n return (\n <button\n key={lbl}\n onClick={() => { popChrome(popKey); pickNodeScale(v); }}\n aria-pressed={nodeScale === v}\n data-topo-chrome-nodesize={lbl}\n data-topo-chrome-nodesize-active={nodeScale === v ? 'true' : 'false'}\n data-topo-chrome-nodesize-popping={chromePopping === popKey ? 'true' : 'false'}\n title={`Node size: ${lbl === 'S' ? 'small' : lbl === 'M' ? 'medium' : 'large'}`}\n // Round 179 / Loop: nodeSize S/M/L active-button hover\n // variant closes the inconsistency with R163 layout\n // toggle and R178 fullscreen. Pre-R179 the active\n // (selected) S/M/L button had bg-cyan-500/15 + text-\n // cyan-300 but NO hover response — the chip looked\n // 'locked', not 'still interactive'. R163 and R178\n // both add hover:bg-cyan-500/20 on the active variant\n // so the active chip stays responsive to mouse. R179\n // closes the trio so all three chrome active-cyan\n // surfaces ship the same gesture.\n // Round 196 / Loop: nodeSize buttons pick up press-state\n // (active:) — selected variant deepens to cyan-500/25,\n // unselected to white/10. Same tier pattern as R196 layout\n // toggle + zoom/reset/fullscreen below.\n // Round 250 / Loop: nodeSize buttons close the chrome-pop\n // family — every clickable chrome button now fires the\n // R186 .anet-chrome-pop scale-pulse on release. R171\n // canvas crossfade (nodeSizeSwitching) keeps masking the\n // node radius change at the global scope; R250 chrome-pop\n // adds the LOCAL button-level confirmation. The two\n // happen simultaneously without conflict — local scale\n // pulse on the button, global canvas dim around it.\n // Milestone round: the entire chrome strip (zoom -/+,\n // ring/grid, fullscreen, S/M/L) now speaks one\n // consistent click vocabulary.\n /* Round 270 / Loop: nodeSize INACTIVE buttons align with\n the Layout toggle's R163 hover-preview pattern. Pre-\n R270 inactive S/M/L used `hover:bg-white/5\n active:bg-white/10` (neutral white tint) while the\n Layout toggle's inactive Ring/Grid uses `hover:bg-\n cyan-500/5 active:bg-cyan-500/15` (faint cyan ghost\n that previews what the active state will look like —\n the active variant is bg-cyan-500/15). Two different\n hover vocabularies for visually-analogous toggle\n controls. R270 unifies inactive toggle hover to\n cyan so all TOGGLE chrome buttons (Layout / nodeSize\n / fullscreen) preview their active state on hover.\n Pure actions (zoom -/+, reset) stay white — they\n aren't toggles, have no active state to preview. */\n className={`px-2 py-1 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset ${idx > 0 ? 'border-l' : ''} ${nodeScale === v ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'hover:bg-cyan-500/5 active:bg-cyan-500/15'}${chromePopping === popKey ? ' anet-chrome-pop' : ''}`}\n style={{ color: nodeScale === v ? undefined : pal.legendText, borderColor: pal.containerBorder }}\n >\n {lbl}\n </button>\n );\n })}\n </div>\n {/* Round 255 / Loop: semantic gap between the fleet-control group\n (node size S/M/L) and the view-control group (zoom / reset /\n fullscreen). Pre-R255 the four groups sat at uniform gap-1.5\n (6px); the spatial signal read as \"4 separate things\" instead\n of \"1 fleet control + 3 view controls\". Doubling the gap before\n the first view-control (ml-1.5 = 6px stacks on top of the\n parent's gap-1.5 = 6px, total 12px) communicates the semantic\n boundary through proximity alone — classic \"law of proximity\"\n layout polish, no extra chrome, no new visual elements.\n data-topo-chrome-view-group-leader marks the boundary surface\n for the test probe; data-topo-chrome-fleet-group-trailer marks\n the nodeSize wrapper's right edge for the gap measurement. */}\n <div\n className=\"ml-1.5 flex items-center rounded-md border overflow-hidden\"\n style={{\n background: pal.legendBox.fill,\n borderColor: pal.containerBorder,\n transition: 'background-color 200ms ease-out, border-color 200ms ease-out',\n }}\n data-topo-chrome-view-group-leader\n data-topo-chrome-zoom-wrapper\n >\n <button\n onClick={() => { popChrome('zoom-out'); zoomByDiscrete(1 / 1.2); }}\n data-topo-chrome-zoom-out\n data-topo-chrome-zoom-out-popping={chromePopping === 'zoom-out' ? 'true' : 'false'}\n // R196: press-state deepens bg one tier above hover (white/5\n // → white/10) so mouse-down has a tactile dim before the\n // R186 icon pop fires on release.\n className=\"px-2 py-1 hover:bg-white/5 active:bg-white/10 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset\"\n style={{ color: pal.legendText }}\n aria-label=\"Zoom out\"\n title=\"Zoom out (−)\"\n >\n {/* R186: icon pop on click. CSS animation runs once;\n React removes the class after 240ms so a quick\n re-click can replay. */}\n <svg\n width=\"12\" height=\"12\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\"\n aria-hidden\n className={chromePopping === 'zoom-out' ? 'anet-chrome-pop' : undefined}\n data-topo-chrome-zoom-out-icon\n ><path d=\"M5 12h14\" /></svg>\n </button>\n {/* Round 192 / Loop: zoom-level readout span participates in the\n R186 click-feel pop alongside the +/− icons. Pre-R192 a click\n on + or − triggered:\n · icon pop (R186, ~220ms scale 1→1.06→1)\n · canvas crossfade (R169, ~280ms opacity blend)\n · readout text snap (instant — 100% → 120%)\n The readout was the only surface that didn't acknowledge the\n gesture. Reusing the existing .anet-chrome-pop CSS keyframe\n (no new keyframes) lets the \"%\" number gently bounce in\n sync with the icon — same 0.22s ease-out, transform-origin\n center. transform-box: fill-box on the keyframe is\n SVG-specific and harmlessly ignored on this HTML span. The\n base layout classes (px / border / tabular-nums / minWidth)\n stay intact; only when chromePopping is 'zoom-in' or\n 'zoom-out' do we splice in the animation class. Same\n React-clears-after-240ms cleanup R186 already runs, so the\n class can replay on a repeat click. */}\n {/* Round 312 / Loop: chrome strip zoom readout '{N}%'\n picks up `font-medium` (500). Extends the R309-R311\n 'data digit weighs more than label' rule to the\n chrome strip's one data display — the zoom\n percentage. Every other chrome strip text is a\n control (S/M/L buttons, zoom +/-, reset, fullscreen,\n Ring/Grid labels); the percent readout is the only\n live DATA. font-medium (not 600 like the SVG panel\n counts) is a tier below because the readout sits in\n HTML chrome context (lighter visual baseline) where\n 500 reads as 'noticeably data-prominent' without\n competing with the SVG panel counts. tabular-nums\n + minWidth 46 stay (R225 family), the existing R264\n color/border transitions stay, the R186 chrome-pop\n class still toggles on zoom-click. */}\n <span\n className={`px-2 py-1 tabular-nums font-medium border-x text-center${\n chromePopping === 'zoom-in' || chromePopping === 'zoom-out'\n ? ' anet-chrome-pop' : ''\n }`}\n data-topo-chrome-zoom-level\n data-topo-chrome-zoom-level-popping={\n chromePopping === 'zoom-in' || chromePopping === 'zoom-out'\n ? 'true' : 'false'\n }\n style={{\n color: pal.legendText,\n borderColor: pal.containerBorder,\n minWidth: 46,\n display: 'inline-block',\n /* Round 264 / Loop: zoom level readout gains theme-toggle\n transition. The span has theme-driven color (pal.\n legendText) + border-x (pal.containerBorder via the\n inline borderColor) but className lacks transition-\n colors — the readout's text + side dividers snapped\n on theme flip while siblings eased. Sibling treatment\n to the nodeSize + zoom wrapper transitions added this\n round. */\n transition: 'color 200ms ease-out, border-color 200ms ease-out',\n }}\n title=\"Current zoom level\"\n >\n {Math.round(view.zoom * 100)}%\n </span>\n <button\n onClick={() => { popChrome('zoom-in'); zoomByDiscrete(1.2); }}\n data-topo-chrome-zoom-in\n data-topo-chrome-zoom-in-popping={chromePopping === 'zoom-in' ? 'true' : 'false'}\n // R196: press-state (mirror of zoom-out above).\n className=\"px-2 py-1 hover:bg-white/5 active:bg-white/10 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset\"\n style={{ color: pal.legendText }}\n aria-label=\"Zoom in\"\n title=\"Zoom in (+)\"\n >\n {/* R186: icon pop on click. Same one-shot CSS animation\n as zoom-out; React removes the class after 240ms. */}\n <svg\n width=\"12\" height=\"12\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\"\n aria-hidden\n className={chromePopping === 'zoom-in' ? 'anet-chrome-pop' : undefined}\n data-topo-chrome-zoom-in-icon\n ><path d=\"M12 5v14M5 12h14\" /></svg>\n </button>\n </div>\n <button\n onClick={() => { armResetSpin(); resetView(); }}\n data-topo-chrome-reset\n data-topo-chrome-reset-spinning={resetSpinning ? 'true' : 'false'}\n // R196: press-state deepens before R184 reset-spin fires on\n // release — mouse-down dim then 450ms spin = full handshake.\n className=\"p-1.5 rounded-md border hover:bg-white/5 active:bg-white/10 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60\"\n style={{ background: pal.legendBox.fill, borderColor: pal.containerBorder, color: pal.legendText }}\n aria-label=\"Reset view\"\n title=\"Reset zoom + pan (0, or double-click the canvas)\"\n >\n {/* R184: the refresh-arrow icon does one counter-clockwise\n rotation on click. CSS animation runs once; React removes\n the className after 460ms (just past the 450ms duration)\n so a subsequent click can replay. */}\n {/* Round 288 / Loop: reset icon strokeWidth 2 → 2.5 unifies\n the chrome icon weight family. Pre-R288 zoom-in / zoom-\n out icons rendered at strokeWidth 2.5 while reset +\n fullscreen icons sat thinner at strokeWidth 2 — five\n chrome icons in a single horizontal strip with two\n weights is exactly the inconsistency R268 closed for\n border colors. Same unification idiom now applied to\n icon strokes: zoom (2.5) + reset (2.5) + fullscreen\n (2.5) all share one weight. View-box (24×24) and\n display size (13×13) unchanged, so geometry stays\n pixel-stable — only the stroke deepens. */}\n <svg\n width=\"13\" height=\"13\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\"\n strokeLinecap=\"round\" strokeLinejoin=\"round\"\n aria-hidden\n className={resetSpinning ? 'anet-reset-spin' : undefined}\n data-topo-chrome-reset-icon\n >\n <path d=\"M3 12a9 9 0 1 0 9-9 9 9 0 0 0-6.4 2.6L3 8\" />\n <path d=\"M3 3v5h5\" />\n </svg>\n </button>\n {/* Round 178 / Loop: fullscreen chrome button picks up the\n active-state visual indicator R163 introduced for the\n Ring/Grid layout toggle. Pre-R178 the button changed\n icon when isFullscreen flipped but its background +\n foreground stayed unchanged — operators in fullscreen\n didn't get a strong 'you're in fullscreen' cue. Adding\n the bg-cyan-500/15 + text-cyan-300 active variant\n mirrors R163's pattern; hover variants tier 1\n brighter (cyan-500/20) when active so the chip\n continues to respond to mouse. Inline style now omits\n background + color when active so the Tailwind cyan\n classes win specificity. */}\n <button\n onClick={() => { popChrome('fullscreen'); toggleFullscreen(); }}\n data-topo-chrome-fullscreen\n data-topo-chrome-fullscreen-active={isFullscreen ? 'true' : 'false'}\n data-topo-chrome-fullscreen-popping={chromePopping === 'fullscreen' ? 'true' : 'false'}\n // R196: fullscreen also picks up press-state — active variant\n // deepens cyan-500/20 → cyan-500/25 on press; non-active\n // deepens white/5 → white/10.\n // R249: chrome-pop on click — same one-vocabulary click signal\n // as layout toggle and zoom buttons.\n /* Round 270 / Loop: fullscreen INACTIVE picks up the cyan\n hover-preview pattern from the Layout toggle. The\n fullscreen button is a TOGGLE (enter/exit fullscreen) so\n its inactive state benefits from the same \"hover previews\n active state\" idiom R163 designed. Sibling treatment to\n the nodeSize buttons at line ~6711. */\n className={`p-1.5 rounded-md border transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 ${\n isFullscreen\n ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25'\n : 'hover:bg-cyan-500/5 active:bg-cyan-500/15'\n }${chromePopping === 'fullscreen' ? ' anet-chrome-pop' : ''}`}\n style={{\n borderColor: pal.containerBorder,\n ...(isFullscreen\n ? {}\n : { background: pal.legendBox.fill, color: pal.legendText }),\n }}\n aria-label={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}\n title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}\n >\n {/* R288 / Loop: fullscreen enter + exit icons strokeWidth\n 2 → 2.5 — same chrome-icon weight unification described\n at the reset icon above. data-topo-chrome-fullscreen-\n icon attribute exposes BOTH variants (entered / exited)\n for the round's stroke-width regression probe. */}\n {isFullscreen ? (\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden data-topo-chrome-fullscreen-icon=\"exit\">\n <path d=\"M8 3v4a1 1 0 0 1-1 1H3M21 8h-4a1 1 0 0 1-1-1V3M3 16h4a1 1 0 0 1 1 1v4M16 21v-4a1 1 0 0 1 1-1h4\" />\n </svg>\n ) : (\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden data-topo-chrome-fullscreen-icon=\"enter\">\n <path d=\"M3 8V5a2 2 0 0 1 2-2h3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M21 16v3a2 2 0 0 1-2 2h-3\" />\n </svg>\n )}\n </button>\n </div>\n\n {/* Issue #100/#106: draggable, resizable singleton chat popover.\n position:fixed so it floats above the page (overflow-hidden here\n doesn't clip fixed children). Rendered *inside* the container so\n that when the graph goes fullscreen (#81) the popover joins the\n fullscreen subtree and stays visible — a sibling render would be\n outside the fullscreened element and disappear. */}\n {chatAlias && (\n <ChatPopover alias={chatAlias} onClose={() => setChatAlias(null)} />\n )}\n </div>\n </section>\n );\n}\n","'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { AliasAvatar } from './AliasAvatar';\nimport { TaskChatPanel } from './TaskChatPanel';\nimport { useSessions } from '../lib/hooks';\nimport { relativeAgo } from '../lib/time';\n\ninterface ChatPopoverProps {\n /** Node alias to chat with. Changing it switches the conversation. */\n alias: string;\n onClose: () => void;\n}\n\nconst MAX_W = 380;\nconst MAX_H = 520;\nconst MIN_W = 300;\nconst MIN_H = 320;\nconst MARGIN = 16;\nconst MOBILE_BP = 640;\n\n/**\n * Issue #100: a floating, draggable chat window opened by clicking a node\n * avatar in the topology graph. Singleton — the parent (TopoGraph) keeps a\n * single `chatAlias`, so clicking another node just swaps `alias` here and\n * the conversation switches in place.\n *\n * Issue #106: a bottom-right handle resizes the window (drag to change w/h,\n * clamped to MIN_W/MIN_H .. the viewport). Coexists with the header move-drag\n * and the close button — each interaction lives on a distinct element and\n * stops its own pointerdown from reaching the others.\n *\n * One consistent design across viewports: a floating draggable card sized to\n * fit (`min(380, vw-32) × min(520, vh-96)`). On a phone it ends up near\n * full-width, so it's docked low on open — the graph above stays visible, and\n * to chat with a different node you drag it down / tap an exposed avatar.\n *\n * The chat body reuses TaskChatPanel's `inline` mode — send / SSE-receive /\n * history are already solved there; this component only adds the floating\n * shell + drag/resize behaviour.\n */\n/** Round 37 / Loop: surface node metadata (cwd + last-seen) inside the\n * ChatPopover header. The SVG <title> tooltip (Rounds 33-34) only shows\n * on hover-over-node, which is lost once the popover is open and\n * potentially dragged away. Lifting cwd and last-seen into the popover\n * header keeps the context where the user actually needs it.\n *\n * Round 38: relativeAgo factored to app/lib/time.ts so this file shares\n * the same TZ-safe parser as TopoGraph (was a duplicated mirror until\n * this round). */\n\nexport function ChatPopover({ alias, onClose }: ChatPopoverProps) {\n // Position + size are resolved on mount (SSR-safe defaults here).\n const [pos, setPos] = useState({ x: 0, y: 0 });\n const [size, setSize] = useState({ w: MAX_W, h: MAX_H });\n const dragRef = useRef<{ active: boolean; startX: number; startY: number; baseX: number; baseY: number }>({\n active: false, startX: 0, startY: 0, baseX: 0, baseY: 0,\n });\n const resizeRef = useRef<{ active: boolean; startX: number; startY: number; baseW: number; baseH: number }>({\n active: false, startX: 0, startY: 0, baseW: 0, baseH: 0,\n });\n\n // Round 37 / Loop: pull the target session out of the SWR cache that\n // TopoGraph already populates — same URL, so no extra network fetch.\n const { sessions } = useSessions();\n const session = useMemo(() => sessions.find(s => s.alias === alias), [sessions, alias]);\n const isOnline = !!session && session.status !== 'offline';\n const lastSeenLine = !isOnline ? relativeAgo(session?.last_seen_at) : null;\n\n const clamp = useCallback((x: number, y: number, w: number, h: number) => {\n const maxX = Math.max(MARGIN, window.innerWidth - w - MARGIN);\n const maxY = Math.max(MARGIN, window.innerHeight - h - MARGIN);\n return {\n x: Math.min(Math.max(MARGIN, x), maxX),\n y: Math.min(Math.max(MARGIN, y), maxY),\n };\n }, []);\n\n useEffect(() => {\n const place = () => {\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n const w = Math.min(MAX_W, vw - MARGIN * 2);\n const h = Math.min(MAX_H, vh - MARGIN * 6);\n setSize({ w, h });\n const mobile = vw < MOBILE_BP;\n // Desktop: top-right of the graph. Mobile: docked low so the graph\n // above stays visible — switch nodes by tapping an exposed avatar.\n const x = mobile ? MARGIN : vw - w - 24;\n const y = mobile ? vh - h - MARGIN : 96;\n setPos(clamp(x, y, w, h));\n };\n place();\n // Keep the popover on-screen if the window is resized.\n window.addEventListener('resize', place);\n return () => window.removeEventListener('resize', place);\n }, [clamp]);\n\n // Esc closes — matches the rest of the dashboard's overlay convention.\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [onClose]);\n\n const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {\n if (e.button !== 0) return;\n (e.currentTarget as Element).setPointerCapture?.(e.pointerId);\n dragRef.current = { active: true, startX: e.clientX, startY: e.clientY, baseX: pos.x, baseY: pos.y };\n };\n const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {\n const d = dragRef.current;\n if (!d.active) return;\n setPos(clamp(d.baseX + (e.clientX - d.startX), d.baseY + (e.clientY - d.startY), size.w, size.h));\n };\n const onPointerUp = (e: React.PointerEvent<HTMLDivElement>) => {\n if (!dragRef.current.active) return;\n dragRef.current.active = false;\n try { (e.currentTarget as Element).releasePointerCapture?.(e.pointerId); } catch {}\n };\n\n // Resize from the bottom-right handle. stopPropagation keeps the header\n // move-drag out of it; the popover is top-left anchored so growing it can\n // only push the bottom/right edges, which we clamp to the viewport.\n const onResizeDown = (e: React.PointerEvent<HTMLDivElement>) => {\n if (e.button !== 0) return;\n e.stopPropagation();\n (e.currentTarget as Element).setPointerCapture?.(e.pointerId);\n resizeRef.current = { active: true, startX: e.clientX, startY: e.clientY, baseW: size.w, baseH: size.h };\n };\n const onResizeMove = (e: React.PointerEvent<HTMLDivElement>) => {\n const r = resizeRef.current;\n if (!r.active) return;\n e.stopPropagation();\n const maxW = Math.max(MIN_W, window.innerWidth - pos.x - MARGIN);\n const maxH = Math.max(MIN_H, window.innerHeight - pos.y - MARGIN);\n setSize({\n w: Math.min(maxW, Math.max(MIN_W, r.baseW + (e.clientX - r.startX))),\n h: Math.min(maxH, Math.max(MIN_H, r.baseH + (e.clientY - r.startY))),\n });\n };\n const onResizeUp = (e: React.PointerEvent<HTMLDivElement>) => {\n if (!resizeRef.current.active) return;\n resizeRef.current.active = false;\n e.stopPropagation();\n try { (e.currentTarget as Element).releasePointerCapture?.(e.pointerId); } catch {}\n };\n\n return (\n <div\n className=\"fixed z-50 flex flex-col overflow-hidden rounded-xl border border-[var(--border)] bg-[var(--bg)] shadow-2xl shadow-black/60 anet-fade-in\"\n style={{ left: pos.x, top: pos.y, width: size.w, height: size.h }}\n role=\"dialog\"\n aria-label={`Chat with ${alias}`}\n >\n {/* Drag handle — the whole header bar moves the window. */}\n <div\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerCancel={onPointerUp}\n className=\"flex items-center justify-between px-3 py-2.5 border-b border-[var(--border)] bg-[var(--bg-secondary)] rounded-t-xl cursor-grab active:cursor-grabbing select-none touch-none\"\n >\n <div className=\"flex items-center gap-2.5 min-w-0\">\n <AliasAvatar alias={alias} size={28} />\n <div className=\"min-w-0\">\n <div className=\"text-sm font-semibold text-[var(--fg)] truncate\">{alias}</div>\n {/* Round 37: cwd / last-seen lines surface the same metadata\n the SVG <title> tooltip carries (rounds 33-34), but inside\n the popover where it stays accessible after dragging the\n window away from the node. Fall back to the drag-hint\n when no metadata is reported. */}\n {session?.project_dir ? (\n <div className=\"text-[10px] text-[var(--fg-muted)] truncate font-mono\" title={session.project_dir} data-popover-cwd>\n cwd: {session.project_dir}\n </div>\n ) : null}\n {lastSeenLine ? (\n <div className=\"text-[10px] text-[var(--fg-muted)] truncate\" data-popover-lastseen>\n last seen: {lastSeenLine}\n </div>\n ) : null}\n {!session?.project_dir && !lastSeenLine ? (\n <div className=\"text-[10px] text-[var(--fg-muted)]\">Drag to move · Esc to close</div>\n ) : null}\n </div>\n </div>\n <button\n onClick={onClose}\n // Without this the header's drag handler captures the pointer and\n // Chromium retargets the click to the header — the button never fires.\n onPointerDown={(e) => e.stopPropagation()}\n aria-label=\"Close chat\"\n className=\"text-[var(--fg-muted)] hover:text-[var(--fg)] p-1.5 rounded-lg hover:bg-[var(--bg-elevated)] shrink-0\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n {/* Chat body — TaskChatPanel inline mode owns send / SSE / history. */}\n <div className=\"flex-1 min-h-0\">\n <TaskChatPanel alias={alias} onClose={onClose} inline />\n </div>\n\n {/* Issue #106: bottom-right resize handle. */}\n <div\n onPointerDown={onResizeDown}\n onPointerMove={onResizeMove}\n onPointerUp={onResizeUp}\n onPointerCancel={onResizeUp}\n aria-label=\"Resize chat\"\n className=\"absolute bottom-0 right-0 w-5 h-5 cursor-nwse-resize touch-none z-10\"\n style={{ touchAction: 'none' }}\n >\n <svg className=\"absolute bottom-0.5 right-0.5 w-3 h-3 text-[var(--fg-dim)]\" viewBox=\"0 0 12 12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M11 5L5 11M11 9L9 11\" />\n </svg>\n </div>\n </div>\n );\n}\n","/** Issue #96: node visual identity — model vendor + runtime.\n *\n * Single source for two orthogonal node dimensions:\n * - vendor : which model house powers the node (avatar body)\n * - runtime: which execution shell it runs in (corner badge)\n *\n * Both are driven by the server `model` / `runtime` fields added in\n * commhub-server 0.8.1-preview.0 (#96 Phase 1, commit 08482ef). Old nodes\n * report `model: null` / `runtime: null` and fall back gracefully — never a\n * broken image or blank avatar.\n *\n * Adding a vendor = one entry in VENDOR_RULES. Dropping a real logo file into\n * public/vendors/ + setting `logo` lights it up with zero render-code change;\n * until then a vendor-tinted monogram stands in (not a fake official logo).\n */\n\nexport interface VendorIdentity {\n id: string;\n label: string;\n /** Monogram fallback colours, used when `logo` is null. */\n mono: { bg: string; ring: string; text: string };\n /** 1-char monogram shown when there's no logo asset. */\n initial: string;\n /** Packaged logo asset path, or null → render the monogram instead. */\n logo: string | null;\n}\n\nconst UNKNOWN_VENDOR: VendorIdentity = {\n id: 'unknown',\n label: 'Unknown vendor',\n mono: { bg: 'hsl(220 10% 26%)', ring: 'hsl(220 10% 46%)', text: 'hsl(220 14% 82%)' },\n initial: '?',\n logo: null,\n};\n\n// Ordered prefix rules — first match wins. `test` runs against a lowercased\n// model id. Keep the most specific prefixes first.\nconst VENDOR_RULES: Array<{ test: (m: string) => boolean; vendor: VendorIdentity }> = [\n {\n test: (m) => m.startsWith('intern'),\n vendor: {\n id: 'intern',\n label: '书生 · 上海 AI 实验室',\n mono: { bg: 'hsl(28 38% 24%)', ring: 'hsl(32 45% 52%)', text: 'hsl(34 60% 82%)' },\n initial: '书',\n // #79 shipped this asset — reuse it as the 书生 vendor logo.\n logo: '/intern_avatar.png',\n },\n },\n {\n test: (m) => m.startsWith('minimax'),\n vendor: {\n id: 'minimax',\n label: 'MiniMax',\n mono: { bg: 'hsl(18 50% 26%)', ring: 'hsl(18 65% 52%)', text: 'hsl(20 80% 82%)' },\n initial: 'M',\n // P0 (Vincent 5222) custom-designed vendor badge — NOT a copy of\n // the MiniMax trademark; geometric min/max zigzag in their warm-\n // red palette. Replaces plain-letter \"M\" fallback.\n logo: '/vendors/minimax.svg',\n },\n },\n {\n test: (m) => m.startsWith('claude'),\n vendor: {\n id: 'anthropic',\n label: 'Anthropic',\n mono: { bg: 'hsl(16 32% 26%)', ring: 'hsl(16 48% 54%)', text: 'hsl(18 60% 84%)' },\n initial: 'A',\n // P0 (Vincent 5222) custom-designed vendor badge — NOT a copy of\n // the Anthropic trademark; 4-pointed sparkle in their warm-orange\n // palette evokes AI/Claude without imitating the official mark.\n // Real Anthropic logo still pending Vincent-direct asset OK.\n logo: '/vendors/claude.svg',\n },\n },\n {\n test: (m) => m.startsWith('gpt') || m.startsWith('codex') || m.startsWith('o1') || m.startsWith('o3') || m.startsWith('o4'),\n vendor: {\n id: 'openai',\n label: 'OpenAI',\n mono: { bg: 'hsl(165 26% 22%)', ring: 'hsl(165 40% 44%)', text: 'hsl(165 45% 80%)' },\n initial: 'O',\n // P0 (Vincent 5222) custom-designed vendor badge — NOT a copy of\n // the OpenAI trademark; hexagonal frame + center dot in their\n // teal palette evokes geometric AI lattice without imitating the\n // knot. Real OpenAI logo still pending Vincent-direct asset OK.\n logo: '/vendors/openai.svg',\n },\n },\n];\n\n/** Resolve a model id to its vendor identity. `null` / unmatched → UNKNOWN. */\nexport function vendorForModel(model: string | null | undefined): VendorIdentity {\n if (!model) return UNKNOWN_VENDOR;\n const m = model.toLowerCase();\n for (const rule of VENDOR_RULES) {\n if (rule.test(m)) return rule.vendor;\n }\n return UNKNOWN_VENDOR;\n}\n\nexport type Runtime = 'claude-code-cli' | 'codex-sdk' | 'claude-agent-sdk' | 'http-api';\n\nexport interface RuntimeIdentity {\n label: string;\n /** SVG path(s) drawn inside a 0..24 viewBox for the corner badge. */\n iconPath: string;\n /** Badge accent colour — kept clear of working/idle/offline status hues. */\n color: string;\n}\n\nconst RUNTIME_MAP: Record<Runtime, RuntimeIdentity> = {\n // terminal / CLI\n 'claude-code-cli': {\n label: 'Claude Code CLI',\n iconPath: 'M4 5h16v14H4z M7 9l3 3-3 3 M13 15h4',\n color: '#a78bfa',\n },\n // code-block / SDK\n 'codex-sdk': {\n label: 'Codex SDK',\n iconPath: 'M9 7l-5 5 5 5 M15 7l5 5-5 5',\n color: '#38bdf8',\n },\n // SDK, distinct hue from codex\n 'claude-agent-sdk': {\n label: 'Claude Agent SDK',\n iconPath: 'M4 7h16v10H4z M8 11h8 M8 14h5',\n color: '#34d399',\n },\n // cloud / API — non-resident process\n 'http-api': {\n label: 'HTTP API',\n iconPath: 'M7 18a4 4 0 010-8 5 5 0 019.6-1.3A3.5 3.5 0 0118 18z',\n color: '#fbbf24',\n },\n};\n\n/** Resolve a server runtime string to badge identity. Unknown → null. */\nexport function runtimeIdentity(runtime: string | null | undefined): RuntimeIdentity | null {\n if (!runtime) return null;\n return RUNTIME_MAP[runtime as Runtime] ?? null;\n}\n\n/** Compact one-line identity for hover / detail surfaces:\n * \"Anthropic · claude-opus-4 · Claude Code CLI\". Pieces with no data drop. */\nexport function identityLine(model: string | null | undefined, runtime: string | null | undefined): string {\n const v = vendorForModel(model);\n const r = runtimeIdentity(runtime);\n const parts: string[] = [];\n if (v.id !== 'unknown') parts.push(v.label);\n if (model) parts.push(model);\n if (r) parts.push(r.label);\n return parts.join(' · ');\n}\n","'use client';\n\nimport Link from 'next/link';\nimport { Session } from './types';\nimport { timeAgo } from './utils';\nimport { AliasAvatar } from './AliasAvatar';\n\ninterface AgentCardProps {\n session: Session;\n hasSse: boolean;\n sseCount: number;\n onChat?: (alias: string) => void;\n}\n\nconst STATUS_CONFIG: Record<string, { bg: string; text: string; dot: string; glow: string }> = {\n working: { bg: 'bg-green-900/30 border-green-800/30', text: 'text-green-300', dot: 'bg-green-500', glow: 'shadow-green-500/10' },\n idle: { bg: 'bg-cyan-900/30 border-cyan-800/30', text: 'text-cyan-300', dot: 'bg-cyan-400', glow: 'shadow-cyan-500/5' },\n blocked: { bg: 'bg-yellow-900/30 border-yellow-800/30', text: 'text-yellow-300', dot: 'bg-yellow-500', glow: '' },\n error: { bg: 'bg-red-900/30 border-red-800/30', text: 'text-red-300', dot: 'bg-red-500', glow: '' },\n};\n\nconst DEFAULT_STATUS = { bg: 'bg-gray-800/50 border-gray-700/30', text: 'text-gray-500', dot: 'bg-gray-500', glow: '' };\n\nexport function AgentCard({ session: s, hasSse, sseCount, onChat }: AgentCardProps) {\n const cfg = hasSse ? (STATUS_CONFIG[s.status] || DEFAULT_STATUS) : DEFAULT_STATUS;\n\n return (\n <Link\n href={`/node?alias=${encodeURIComponent(s.alias)}`}\n prefetch={false}\n className={`anet-agent-card group relative block rounded-xl border p-4 transition-all duration-300 cursor-pointer hover:-translate-y-0.5 ${\n hasSse\n ? `bg-[#111128] border-[#2a2a4a] hover:border-cyan-500/30 hover:shadow-lg ${cfg.glow}`\n : 'bg-[#0d0d1a] border-[#1a1a2a] opacity-40'\n }`}\n >\n {/* Header: avatar + name + status. Avatar carries the alias→hue map\n shared with Messages/Nodes/Tasks/Overview; the live status dot\n stays as a small pulse-capable indicator. */}\n <div className=\"flex items-center justify-between mb-3\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <AliasAvatar alias={s.alias} size={22} />\n <span className=\"font-semibold text-white truncate text-sm\" title={s.alias}>{s.alias}</span>\n <span className={`inline-block w-1.5 h-1.5 rounded-full shrink-0 ${cfg.dot} ${hasSse && s.status === 'working' ? 'animate-pulse' : ''}`} />\n </div>\n <span className={`text-[11px] px-2 py-0.5 rounded-md border shrink-0 ${cfg.bg} ${cfg.text}`}>\n {hasSse ? s.status : 'offline'}\n </span>\n </div>\n\n {/* Agent type badge */}\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"text-xs text-gray-600 bg-[#0a0a15] px-2 py-0.5 rounded border border-[#1a1a2a]\">\n {s.agent || 'unknown'}\n </span>\n {hasSse && (\n <span className=\"text-[10px] text-green-500\">SSE:{sseCount}</span>\n )}\n </div>\n\n {/* Task */}\n {s.task ? (\n <div className=\"text-xs text-gray-400 bg-[#0a0a15] rounded-lg px-3 py-2 border border-[#1a1a2a] line-clamp-2\" title={s.task}>\n {s.task}\n </div>\n ) : (\n <div className=\"text-xs text-gray-700 italic\">No active task</div>\n )}\n\n {/* Progress bar */}\n {s.progress > 0 && (\n <div className=\"mt-3\">\n <div className=\"flex justify-between text-[10px] mb-1\">\n <span className=\"text-gray-600\">Progress</span>\n <span className={cfg.text}>{s.progress}%</span>\n </div>\n <div className=\"w-full bg-gray-800 rounded-full h-1.5 overflow-hidden\">\n <div\n className={`h-1.5 rounded-full transition-all duration-700 ${s.status === 'working' ? 'bg-green-500' : 'bg-cyan-500'}`}\n style={{ width: `${Math.min(s.progress, 100)}%` }}\n />\n </div>\n </div>\n )}\n\n {/* Footer: time + chat + hover chevron affordance (round 44).\n The card is a <Link> so it's clickable everywhere, but with no\n visible cue users may not realise. Chevron appears on hover and\n slides right ~2px for a \"drill in\" hint. */}\n <div className=\"mt-3 flex justify-between items-center text-[10px] text-gray-600\">\n <span className=\"truncate\" title={s.server || ''}>{s.server || '--'}</span>\n <div className=\"flex items-center gap-2\">\n {onChat && hasSse && (\n <button\n onClick={e => { e.preventDefault(); e.stopPropagation(); onChat(s.alias); }}\n className=\"text-cyan-400 hover:text-cyan-300 px-1.5 py-0.5 rounded border border-cyan-500/20 hover:bg-cyan-500/10 transition-colors\"\n >\n Chat\n </button>\n )}\n <span>{timeAgo(s.updated_at)}</span>\n <svg\n aria-hidden\n className=\"w-3 h-3 text-gray-700 opacity-0 -translate-x-1 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200\"\n fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"\n >\n <path d=\"M9 18l6-6-6-6\" />\n </svg>\n </div>\n </div>\n </Link>\n );\n}\n","'use client';\n\nimport { InboxMessage } from './types';\nimport { AliasAvatar } from './AliasAvatar';\nimport { timeAgo, previewContent } from './utils';\n\ninterface InboxPanelProps {\n messages: InboxMessage[];\n}\n\nexport function InboxPanel({ messages }: InboxPanelProps) {\n if (messages.length === 0) return null;\n\n return (\n <div className=\"mt-8\">\n <h2 className=\"text-lg font-semibold text-white mb-3 flex items-center gap-2\">\n <span className=\"text-gray-500\">Inbox</span>\n <span className=\"text-xs bg-blue-900/30 text-blue-400 px-2 py-0.5 rounded-full border border-blue-800/30\">\n {messages.length}\n </span>\n </h2>\n <div className=\"space-y-2 max-h-96 overflow-y-auto pr-1 scrollbar-thin\">\n {messages.map(m => (\n <div key={m.id} className=\"bg-[#111128] border border-[#2a2a4a] rounded-lg px-4 py-3 text-sm transition-colors hover:border-[#3a3a5a]\">\n <div className=\"flex items-center gap-2 text-xs mb-1.5\">\n {m.from_session && <AliasAvatar alias={m.from_session} size={16} />}\n <span className=\"text-gray-200 font-medium truncate\">{m.from_session}</span>\n <span className=\"ml-auto text-gray-600 shrink-0\" title={m.created_at}>{timeAgo(m.created_at)}</span>\n </div>\n <div className=\"text-gray-300 leading-relaxed line-clamp-3\" title={m.content || ''}>{previewContent(m.content)}</div>\n </div>\n ))}\n </div>\n </div>\n );\n}\n","'use client';\n\n/**\n * Loading skeleton — mirrors the actual Overview layout structure so the\n * page doesn't appear to \"shift\" once data arrives. Uses the same\n * `anet-skeleton-pulse` rhythm as the brand pulse (1.6s opacity drift,\n * no scale, no blur, no glow) so the loading animation feels native to\n * the rest of the dashboard rather than a generic spinner.\n *\n * Bars are theme-aware: anet-skeleton-bar (existing CSS shim resolves this to\n * var(--bg-elevated) on light/mint), so light theme shows soft grey blocks\n * on white cards and dark theme shows lighter blocks on navy cards.\n */\nexport function LoadingSkeleton() {\n return (\n <div className=\"min-h-screen bg-[#0a0a1a] text-gray-100 p-4 sm:p-6 font-mono\">\n {/* KPI top strip — 4 cards matching StatsBar */}\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-3 mb-8 anet-skeleton-pulse\">\n {[1, 2, 3, 4].map(i => (\n <div key={i} className=\"rounded-xl border border-[#2a2a4a] bg-[#111128] px-4 py-3\">\n <Bar w=\"2.5rem\" h=\"1.75rem\" />\n <Bar w=\"3.5rem\" h=\"0.75rem\" className=\"mt-2\" />\n <Bar w=\"5rem\" h=\"0.625rem\" className=\"mt-1\" />\n </div>\n ))}\n </div>\n\n {/* Dispatch + UserBar row */}\n <div className=\"flex items-center gap-3 mb-3 anet-skeleton-pulse\">\n <Bar w=\"6rem\" h=\"2.5rem\" rounded=\"0.75rem\" />\n <div className=\"flex-1 rounded-lg border border-[#2a2a4a] bg-[#111128] px-4 py-2.5 flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-full anet-skeleton-bar\" />\n <div className=\"flex-1\">\n <Bar w=\"6rem\" h=\"0.875rem\" />\n <Bar w=\"10rem\" h=\"0.625rem\" className=\"mt-1\" />\n </div>\n </div>\n </div>\n\n {/* Config bar */}\n <div className=\"mb-6 rounded-lg border border-[#2a2a4a] bg-[#111128] px-4 py-3 anet-skeleton-pulse\">\n <Bar w=\"14rem\" h=\"0.875rem\" />\n </div>\n\n {/* Stat strip 3 cards */}\n <div className=\"grid grid-cols-3 gap-2 sm:gap-3 mb-3 anet-skeleton-pulse\">\n {[1, 2, 3].map(i => (\n <div key={i} className=\"rounded-xl border border-[#2a2a4a] bg-[#111128] px-3 py-3\">\n <Bar w=\"2rem\" h=\"1.25rem\" />\n <Bar w=\"2.5rem\" h=\"0.75rem\" className=\"mt-1\" />\n <Bar w=\"3.5rem\" h=\"0.625rem\" className=\"mt-px\" />\n </div>\n ))}\n </div>\n\n {/* Nav rail 3 cards */}\n <div className=\"grid grid-cols-3 gap-2 sm:gap-3 mb-6 anet-skeleton-pulse\">\n {[1, 2, 3].map(i => (\n <div key={i} className=\"rounded-xl border border-[#2a2a4a] bg-[#111128] px-3 py-2.5 flex items-center justify-center gap-2\">\n <div className=\"w-4 h-4 rounded anet-skeleton-bar\" />\n <Bar w=\"4rem\" h=\"0.75rem\" />\n </div>\n ))}\n </div>\n\n {/* Broadcast bar */}\n <div className=\"mb-6 flex gap-2 anet-skeleton-pulse\">\n <div className=\"flex-1 h-10 rounded-lg border border-[#2a2a4a] bg-[#111128]\" />\n <div className=\"w-28 h-10 rounded-lg anet-skeleton-bar\" />\n </div>\n\n {/* Agent card grid */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-3 sm:gap-4 anet-skeleton-pulse\">\n {[1, 2, 3, 4].map(i => (\n <div key={i} className=\"rounded-xl border border-[#2a2a4a] bg-[#111128] p-4\">\n <div className=\"flex items-center gap-2 mb-3\">\n <div className=\"w-2 h-2 rounded-full anet-skeleton-bar\" />\n <Bar w=\"6rem\" h=\"0.875rem\" />\n </div>\n <div className=\"space-y-2\">\n <Bar w=\"100%\" h=\"0.625rem\" />\n <Bar w=\"75%\" h=\"0.625rem\" />\n <Bar w=\"55%\" h=\"0.625rem\" />\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n}\n\n/** Single shimmer bar — uses `anet-skeleton-bar` so theme-specific bar\n * color (dark navy on dark themes, mid-grey on light) overrides the\n * default. */\nfunction Bar({ w, h, rounded, className }: { w: string; h: string; rounded?: string; className?: string }) {\n return (\n <div\n className={`anet-skeleton-bar ${className || ''}`}\n style={{ width: w, height: h, borderRadius: rounded || '0.375rem' }}\n />\n );\n}\n\n// EmptyState export removed in 0.4.5 — replaced by EmptyState.tsx with\n// per-variant glyphs and a NodesEmptyState wrapper for the Overview\n// hint-aware behavior. See app/components/EmptyState.tsx.\n","'use client';\n\nimport { useEffect, useState } from 'react';\nimport { useNetworkId } from '../lib/network-context';\n\ninterface User {\n user_id: string;\n username: string;\n display_name: string;\n role: string;\n}\n\ninterface Network {\n network_id: string;\n network_name: string;\n description: string;\n}\n\ninterface AuthState {\n user: User | null;\n networks: Network[];\n currentNetwork: string;\n token: string;\n}\n\nexport function UserBar() {\n const { setNetworkId } = useNetworkId();\n const [auth, setAuth] = useState<AuthState>({ user: null, networks: [], currentNetwork: '', token: '' });\n const [showLogin, setShowLogin] = useState(false);\n const [username, setUsername] = useState('');\n const [password, setPassword] = useState('');\n const [loginError, setLoginError] = useState('');\n const [loginPending, setLoginPending] = useState(false);\n\n // Try to restore from sessionStorage\n useEffect(() => {\n const saved = sessionStorage.getItem('anet_v3_auth');\n if (saved) {\n try { setAuth(JSON.parse(saved)); } catch {}\n }\n }, []);\n\n const doAuth = async (action: 'login' | 'register') => {\n if (!username.trim() || !password.trim()) return;\n setLoginPending(true);\n setLoginError('');\n try {\n const res = await fetch('/api/hub/auth', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action, username, password }),\n });\n const data = await res.json();\n if (!data.ok) { setLoginError(data.error || 'Failed'); setLoginPending(false); return; }\n\n // Fetch user details\n const meRes = await fetch(`/api/hub/auth?token=${data.token}&endpoint=/api/auth/me`);\n const meData = await meRes.json();\n\n const newAuth: AuthState = {\n user: data.user,\n networks: meData.networks || [],\n currentNetwork: meData.current_network?.network_id || meData.networks?.[0]?.network_id || '',\n token: data.token,\n };\n setAuth(newAuth);\n sessionStorage.setItem('anet_v3_auth', JSON.stringify(newAuth));\n setShowLogin(false);\n setUsername('');\n setPassword('');\n } catch { setLoginError('Connection failed'); }\n setLoginPending(false);\n };\n\n const logout = () => {\n setAuth({ user: null, networks: [], currentNetwork: '', token: '' });\n sessionStorage.removeItem('anet_v3_auth');\n };\n\n const [editing, setEditing] = useState(false);\n const [editName, setEditName] = useState('');\n const [editEmail, setEditEmail] = useState('');\n\n const updateProfile = async () => {\n try {\n const res = await fetch(`/api/hub/auth?token=${auth.token}&endpoint=/api/auth/me`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action: 'update_profile', token: auth.token, display_name: editName, email: editEmail }),\n });\n // Use a direct PUT via a new proxy approach\n const putRes = await fetch('/api/hub/auth', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action: 'update_profile', token: auth.token, display_name: editName, email: editEmail }),\n });\n const data = await putRes.json();\n if (data.ok && data.user) {\n const updated = { ...auth, user: data.user };\n setAuth(updated);\n sessionStorage.setItem('anet_v3_auth', JSON.stringify(updated));\n setEditing(false);\n }\n } catch {}\n };\n\n // Don't show anything when not logged in — login page handles that\n if (!auth.user) return null;\n\n const initials = (auth.user.display_name || auth.user.username).slice(0, 2).toUpperCase();\n\n return (\n <div className=\"flex items-center justify-between bg-[#111128] border border-[#2a2a4a] rounded-lg px-4 py-2.5 mb-4\">\n {/* User info */}\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-full bg-gradient-to-br from-cyan-500 to-blue-600 flex items-center justify-center text-xs font-bold text-white\">\n {initials}\n </div>\n <div>\n <div className=\"text-sm text-white font-medium\">{auth.user.display_name || auth.user.username}</div>\n <div className=\"text-[10px] text-gray-500\">{auth.user.role} · {auth.user.user_id}</div>\n </div>\n </div>\n\n {/* Network switcher */}\n <div className=\"flex items-center gap-3\">\n {auth.networks.length > 0 && (\n <select\n value={auth.currentNetwork}\n onChange={e => {\n const updated = { ...auth, currentNetwork: e.target.value };\n setAuth(updated);\n setNetworkId(e.target.value);\n sessionStorage.setItem('anet_v3_auth', JSON.stringify(updated));\n }}\n className=\"bg-[#0a0a15] border border-[#2a2a4a] rounded px-2 py-1 text-xs text-white focus:outline-none\"\n >\n {auth.networks.map(n => (\n <option key={n.network_id} value={n.network_id}>{n.network_name}</option>\n ))}\n </select>\n )}\n <button onClick={() => { setEditName(auth.user?.display_name || ''); setEditEmail(''); setEditing(!editing); }}\n className=\"text-xs text-gray-500 hover:text-gray-300 whitespace-nowrap\"\n aria-label=\"Edit profile\">\n <span className=\"hidden sm:inline\">Edit</span>\n <svg className=\"sm:hidden w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth=\"1.5\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.862 4.487z\" />\n </svg>\n </button>\n <button onClick={logout}\n className=\"text-xs text-gray-500 hover:text-gray-300 whitespace-nowrap\"\n aria-label=\"Sign out\">\n <span className=\"hidden sm:inline\">Sign out</span>\n <svg className=\"sm:hidden w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth=\"1.5\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9\" />\n </svg>\n </button>\n </div>\n {editing && (\n <div className=\"flex flex-wrap items-center gap-2 mt-2 pt-2 border-t border-[#2a2a4a]\">\n <input type=\"text\" value={editName} onChange={e => setEditName(e.target.value)} placeholder=\"Display name\"\n className=\"bg-[#0a0a15] border border-[#2a2a4a] rounded px-2 py-1 text-xs text-white placeholder-gray-600 focus:outline-none w-32\" />\n <input type=\"email\" value={editEmail} onChange={e => setEditEmail(e.target.value)} placeholder=\"Email\"\n className=\"bg-[#0a0a15] border border-[#2a2a4a] rounded px-2 py-1 text-xs text-white placeholder-gray-600 focus:outline-none w-40\" />\n <button onClick={updateProfile} className=\"px-2 py-1 bg-cyan-600 hover:bg-cyan-500 text-white text-xs rounded\">Save</button>\n </div>\n )}\n </div>\n );\n}\n","'use client';\n\nimport { useState } from 'react';\nimport { TaskChatPanel } from './TaskChatPanel';\n\ninterface CommandCenterProps {\n /** Currently open chat tabs */\n tabs: string[];\n activeTab: string;\n onOpenTab: (alias: string) => void;\n onCloseTab: (alias: string) => void;\n onSetActive: (alias: string) => void;\n onClose: () => void;\n}\n\n/**\n * Multi-tab chat panel for commanding multiple agents simultaneously.\n * Wraps TaskChatPanel with a tab bar for switching between agents.\n */\nexport function CommandCenter({ tabs, activeTab, onOpenTab, onCloseTab, onSetActive, onClose }: CommandCenterProps) {\n if (tabs.length === 0) return null;\n\n return (\n <>\n {/* Backdrop */}\n <div className=\"fixed inset-0 bg-black/30 z-40 lg:hidden\" onClick={onClose} />\n\n {/* Panel */}\n <div className=\"fixed top-0 right-0 h-full w-full lg:w-[500px] bg-[#0a0a1a] border-l border-[#2a2a4a] z-50 flex flex-col shadow-2xl shadow-black/60 animate-slide-in\">\n {/* Tab bar */}\n <div className=\"flex items-center border-b border-[#2a2a4a] bg-[#0d0d1a] overflow-x-auto\">\n <div className=\"flex-1 flex min-w-0\">\n {tabs.map(alias => (\n <button\n key={alias}\n onClick={() => onSetActive(alias)}\n className={`flex items-center gap-1.5 px-3 py-2.5 text-xs border-b-2 transition-colors shrink-0 ${\n activeTab === alias\n ? 'border-cyan-400 text-cyan-300 bg-cyan-500/5'\n : 'border-transparent text-gray-500 hover:text-gray-300'\n }`}\n >\n <div className={`w-2 h-2 rounded-full ${activeTab === alias ? 'bg-cyan-400' : 'bg-gray-600'}`} />\n <span className=\"max-w-[80px] truncate\">{alias}</span>\n <button\n onClick={e => { e.stopPropagation(); onCloseTab(alias); }}\n className=\"ml-1 text-gray-600 hover:text-gray-300 p-0.5\"\n >\n ×\n </button>\n </button>\n ))}\n </div>\n <button onClick={onClose} className=\"text-gray-500 hover:text-white px-3 py-2.5 shrink-0 border-l border-[#2a2a4a]\">\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n {/* Active chat - render all but only show active (preserves state) */}\n <div className=\"flex-1 relative overflow-hidden\">\n {tabs.map(alias => (\n <div key={alias} className={`absolute inset-0 ${activeTab === alias ? 'block' : 'hidden'}`}>\n <InlineChat alias={alias} />\n </div>\n ))}\n </div>\n </div>\n\n <style jsx global>{`\n @keyframes slide-in {\n from { transform: translateX(100%); }\n to { transform: translateX(0); }\n }\n .animate-slide-in {\n animation: slide-in 0.2s ease-out;\n }\n `}</style>\n </>\n );\n}\n\n/** Inline chat without the panel chrome (used inside CommandCenter tabs) */\nfunction InlineChat({ alias }: { alias: string }) {\n // Reuse TaskChatPanel's logic but render without the outer frame\n return <TaskChatPanel alias={alias} onClose={() => {}} inline />;\n}\n\n/**\n * Hook to manage multi-tab command center state.\n */\nexport function useCommandCenter() {\n const [tabs, setTabs] = useState<string[]>([]);\n const [activeTab, setActiveTab] = useState('');\n\n const openTab = (alias: string) => {\n setTabs(prev => prev.includes(alias) ? prev : [...prev, alias]);\n setActiveTab(alias);\n };\n\n const closeTab = (alias: string) => {\n setTabs(prev => {\n const next = prev.filter(t => t !== alias);\n if (activeTab === alias) setActiveTab(next[next.length - 1] || '');\n return next;\n });\n };\n\n const closeAll = () => {\n setTabs([]);\n setActiveTab('');\n };\n\n return { tabs, activeTab, openTab, closeTab, closeAll, setActiveTab };\n}\n","'use client';\n\nimport { useState } from 'react';\nimport type { Session } from './types';\nimport { AliasAvatar } from './AliasAvatar';\n\ninterface DispatchPanelProps {\n sessions: Session[];\n onClose: () => void;\n}\n\ninterface SendResult {\n alias: string;\n ok: boolean;\n error?: string;\n}\n\nexport function DispatchPanel({ sessions, onClose }: DispatchPanelProps) {\n const [selected, setSelected] = useState<Set<string>>(new Set());\n const [prompt, setPrompt] = useState('');\n const [priority, setPriority] = useState('normal');\n const [sending, setSending] = useState(false);\n const [results, setResults] = useState<SendResult[]>([]);\n const [filter, setFilter] = useState('');\n\n const onlineNodes = sessions.filter(s => s.status !== 'offline');\n const filtered = onlineNodes.filter(s =>\n !filter || s.alias.toLowerCase().includes(filter.toLowerCase()) || (s.agent || '').toLowerCase().includes(filter.toLowerCase())\n );\n\n const toggleNode = (alias: string) => {\n setSelected(prev => {\n const next = new Set(prev);\n if (next.has(alias)) next.delete(alias); else next.add(alias);\n return next;\n });\n };\n\n const selectAll = () => {\n if (selected.size === filtered.length) {\n setSelected(new Set());\n } else {\n setSelected(new Set(filtered.map(s => s.alias)));\n }\n };\n\n const dispatch = async () => {\n if (!prompt.trim() || selected.size === 0 || sending) return;\n setSending(true);\n setResults([]);\n\n const promises = [...selected].map(async alias => {\n try {\n const res = await fetch('/api/hub/send', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ alias, task: prompt, priority }),\n });\n const data = await res.json();\n return { alias, ok: !!data.ok, error: data.error };\n } catch (e) {\n return { alias, ok: false, error: 'send failed' };\n }\n });\n\n const res = await Promise.all(promises);\n setResults(res);\n setSending(false);\n };\n\n const successCount = results.filter(r => r.ok).length;\n\n return (\n <>\n <div className=\"fixed inset-0 bg-black/50 z-40 anet-fade-in\" onClick={onClose} />\n <div className=\"fixed inset-4 lg:inset-x-[15%] lg:inset-y-[5%] bg-[#0a0a1a] border border-[#2a2a4a] rounded-2xl z-50 flex flex-col shadow-2xl shadow-black/70 overflow-hidden anet-fade-in\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-[#2a2a4a] bg-[#0d0d1a]\">\n <div>\n <h2 className=\"text-lg font-bold text-white\">Dispatch Task</h2>\n <p className=\"text-xs text-gray-500 mt-0.5\">Send a task to one or more agents</p>\n </div>\n <button onClick={onClose} className=\"text-gray-500 hover:text-white p-1.5 rounded-lg hover:bg-[#1a1a2a]\">\n <svg className=\"w-5 h-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div className=\"flex-1 flex flex-col lg:flex-row overflow-hidden\">\n {/* Left: Node selection */}\n <div className=\"lg:w-[280px] border-b lg:border-b-0 lg:border-r border-[#2a2a4a] flex flex-col\">\n <div className=\"px-4 py-3 border-b border-[#2a2a4a]\">\n <input\n type=\"text\" value={filter} onChange={e => setFilter(e.target.value)}\n placeholder=\"Filter agents...\"\n className=\"w-full bg-[#111128] border border-[#2a2a4a] rounded-lg px-3 py-2 text-xs text-white placeholder-gray-600 focus:border-cyan-500/40 focus:outline-none\"\n />\n <div className=\"flex items-center justify-between mt-2\">\n <button onClick={selectAll} className=\"text-[10px] text-cyan-400 hover:text-cyan-300\">\n {selected.size === filtered.length ? 'Deselect all' : `Select all (${filtered.length})`}\n </button>\n <span className=\"text-[10px] text-gray-600\">{selected.size} selected</span>\n </div>\n </div>\n <div className=\"flex-1 overflow-y-auto px-2 py-2 space-y-0.5 max-h-[200px] lg:max-h-none\">\n {filtered.map(s => (\n <button key={s.alias} onClick={() => toggleNode(s.alias)}\n className={`w-full flex items-center gap-2 px-3 py-2 rounded-lg text-xs text-left transition-colors ${\n selected.has(s.alias) ? 'bg-cyan-500/10 text-cyan-300 border border-cyan-500/20' : 'text-gray-400 hover:bg-[#1a1a2a]'\n }`}>\n <AliasAvatar alias={s.alias} size={16} />\n <div className={`w-1.5 h-1.5 rounded-full shrink-0 ${s.status === 'working' ? 'bg-green-400' : s.status === 'idle' ? 'bg-cyan-400' : 'bg-gray-500'}`} />\n <span className=\"truncate flex-1\">{s.alias}</span>\n <span className=\"text-[9px] text-gray-600\">{s.agent || '--'}</span>\n </button>\n ))}\n {filtered.length === 0 && <div className=\"text-center text-xs text-gray-600 py-4\">No online agents</div>}\n </div>\n </div>\n\n {/* Right: Prompt + Send */}\n <div className=\"flex-1 flex flex-col\">\n <div className=\"flex-1 px-6 py-4 flex flex-col\">\n <label className=\"text-xs text-gray-500 uppercase mb-2\">Task Prompt</label>\n <textarea\n value={prompt} onChange={e => setPrompt(e.target.value)}\n placeholder=\"Enter the task you want to dispatch...\"\n className=\"flex-1 min-h-[120px] bg-[#111128] border border-[#2a2a4a] rounded-xl px-4 py-3 text-sm text-white placeholder-gray-600 focus:border-cyan-500/40 focus:outline-none resize-none\"\n />\n\n <div className=\"flex items-center gap-3 mt-4\">\n <select value={priority} onChange={e => setPriority(e.target.value)}\n className=\"bg-[#111128] border border-[#2a2a4a] rounded-lg px-3 py-2 text-xs text-white focus:outline-none\">\n <option value=\"normal\">Normal priority</option>\n <option value=\"high\">High priority</option>\n <option value=\"low\">Low priority</option>\n </select>\n\n <div className=\"flex-1\" />\n\n <button onClick={dispatch} disabled={sending || !prompt.trim() || selected.size === 0}\n className=\"px-6 py-2.5 bg-gradient-to-r from-cyan-600 to-blue-600 hover:from-cyan-500 hover:to-blue-500 disabled:from-gray-800 disabled:to-gray-800 disabled:text-gray-600 text-white text-sm font-medium rounded-xl transition-all shadow-lg shadow-cyan-500/10 disabled:shadow-none active:scale-95\">\n {sending ? (\n <span className=\"flex items-center gap-2\">\n <span className=\"w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin\" />\n Sending...\n </span>\n ) : (\n `Dispatch to ${selected.size} agent${selected.size > 1 ? 's' : ''}`\n )}\n </button>\n </div>\n </div>\n\n {/* Results */}\n {results.length > 0 && (\n <div className=\"px-6 py-3 border-t border-[#2a2a4a] bg-[#0d0d1a]\">\n <div className=\"text-xs text-gray-500 mb-2\">\n {successCount}/{results.length} dispatched successfully\n </div>\n <div className=\"flex flex-wrap gap-1.5\">\n {results.map(r => (\n <span key={r.alias} className={`flex items-center gap-1.5 text-[10px] pl-1 pr-2 py-0.5 rounded-full border ${\n r.ok ? 'text-green-300 border-green-500/20 bg-green-500/5' : 'text-red-300 border-red-500/20 bg-red-500/5'\n }`}>\n <AliasAvatar alias={r.alias} size={14} />\n <span>{r.alias}</span>\n <span aria-hidden>{r.ok ? '✓' : r.error || '✗'}</span>\n </span>\n ))}\n </div>\n </div>\n )}\n </div>\n </div>\n </div>\n </>\n );\n}\n"],"names":["React","_interopDefaultLegacy","e","React__default","_defineProperties","target","props","i","length","descriptor","enumerable","configurable","writable","Object","defineProperty","key","_createClass","Constructor","protoProps","staticProps","prototype","isProd","process","env","isString","o","toString","call","StyleSheet","param","ref","_name","name","_optimizeForSpeed","optimizeForSpeed","invariant$1","_deletedRulePlaceholder","_serverSheet","undefined","_tags","_injected","_rulesCount","node","document","querySelector","_nonce","_proto","setOptimizeForSpeed","bool","flush","inject","isOptimizeForSpeed","_this","cssRules","insertRule","rule","index","cssText","push","deleteRule","getSheetForTag","tag","sheet","styleSheets","ownerNode","getSheet","insertionPoint","replaceRule","trim","error","console","warn","makeStyleTag","cssString","relativeToTag","createElement","setAttribute","type","appendChild","createTextNode","head","getElementsByTagName","insertBefore","get","condition","message","Error","hash","str","_$hash","charCodeAt","stringHash","sanitize","replace","cache","computeId","baseId","propsToString","String","computeSelector","id","css","selectoPlaceholderRegexp","idcss","mapRulesToStyle","options","map","args","nonce","dangerouslySetInnerHTML","__html","StyleSheetRegistry","_styleSheet","styleSheet","_sheet","_fromServer","_indices","_instancesCounts","add","Array","isArray","children","getIdAndRules","styleId","rules","indices","filter","remove","invariant","tagFromServer","parentNode","removeChild","forEach","update","nextProps","fromServer","keys","concat","join","Boolean","styles","dynamic","selectFromServer","elements","slice","querySelectorAll","reduce","acc","element","StyleSheetContext","createContext","displayName","createStyleRegistry","StyleRegistry","configuredRegistry","registry","rootRegistry","useContext","useState","Provider","value","useStyleRegistry","useInsertionEffect","useLayoutEffect","defaultRegistry","JSXStyle","info","tagInfo","exports","style","module","StatsBar","online","working","total","version","uptime","onlinePercent","Math","round","fleetEmpty","className","StatCard","label","sub","color","accent","border","accentKey","split"],"mappings":"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","ignoreList":[0,1]}
|
|
1
|
+
{"version":3,"sources":["../../../../../agent-network-dashboard/node_modules/styled-jsx/dist/index/index.js","../../../../../agent-network-dashboard/node_modules/styled-jsx/style.js","../../../../../agent-network-dashboard/app/page.tsx","../../../../../agent-network-dashboard/app/components/StatsBar.tsx","../../../../../agent-network-dashboard/app/components/BroadcastBar.tsx","../../../../../agent-network-dashboard/app/components/TopoGraph.tsx","../../../../../agent-network-dashboard/app/components/ChatPopover.tsx","../../../../../agent-network-dashboard/app/lib/vendorIdentity.ts","../../../../../agent-network-dashboard/app/components/AgentCard.tsx","../../../../../agent-network-dashboard/app/components/InboxPanel.tsx","../../../../../agent-network-dashboard/app/components/LoadingSkeleton.tsx","../../../../../agent-network-dashboard/app/components/UserBar.tsx","../../../../../agent-network-dashboard/app/components/CommandCenter.tsx","../../../../../agent-network-dashboard/app/components/DispatchPanel.tsx"],"sourcesContent":["require('client-only');\nvar React = require('react');\n\nfunction _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }\n\nvar React__default = /*#__PURE__*/_interopDefaultLegacy(React);\n\n/*\nBased on Glamor's sheet\nhttps://github.com/threepointone/glamor/blob/667b480d31b3721a905021b26e1290ce92ca2879/src/sheet.js\n*/ function _defineProperties(target, props) {\n for(var i = 0; i < props.length; i++){\n var descriptor = props[i];\n descriptor.enumerable = descriptor.enumerable || false;\n descriptor.configurable = true;\n if (\"value\" in descriptor) descriptor.writable = true;\n Object.defineProperty(target, descriptor.key, descriptor);\n }\n}\nfunction _createClass(Constructor, protoProps, staticProps) {\n if (protoProps) _defineProperties(Constructor.prototype, protoProps);\n if (staticProps) _defineProperties(Constructor, staticProps);\n return Constructor;\n}\nvar isProd = typeof process !== \"undefined\" && process.env && process.env.NODE_ENV === \"production\";\nvar isString = function(o) {\n return Object.prototype.toString.call(o) === \"[object String]\";\n};\nvar StyleSheet = /*#__PURE__*/ function() {\n function StyleSheet(param) {\n var ref = param === void 0 ? {} : param, _name = ref.name, name = _name === void 0 ? \"stylesheet\" : _name, _optimizeForSpeed = ref.optimizeForSpeed, optimizeForSpeed = _optimizeForSpeed === void 0 ? isProd : _optimizeForSpeed;\n invariant$1(isString(name), \"`name` must be a string\");\n this._name = name;\n this._deletedRulePlaceholder = \"#\" + name + \"-deleted-rule____{}\";\n invariant$1(typeof optimizeForSpeed === \"boolean\", \"`optimizeForSpeed` must be a boolean\");\n this._optimizeForSpeed = optimizeForSpeed;\n this._serverSheet = undefined;\n this._tags = [];\n this._injected = false;\n this._rulesCount = 0;\n var node = typeof window !== \"undefined\" && document.querySelector('meta[property=\"csp-nonce\"]');\n this._nonce = node ? node.getAttribute(\"content\") : null;\n }\n var _proto = StyleSheet.prototype;\n _proto.setOptimizeForSpeed = function setOptimizeForSpeed(bool) {\n invariant$1(typeof bool === \"boolean\", \"`setOptimizeForSpeed` accepts a boolean\");\n invariant$1(this._rulesCount === 0, \"optimizeForSpeed cannot be when rules have already been inserted\");\n this.flush();\n this._optimizeForSpeed = bool;\n this.inject();\n };\n _proto.isOptimizeForSpeed = function isOptimizeForSpeed() {\n return this._optimizeForSpeed;\n };\n _proto.inject = function inject() {\n var _this = this;\n invariant$1(!this._injected, \"sheet already injected\");\n this._injected = true;\n if (typeof window !== \"undefined\" && this._optimizeForSpeed) {\n this._tags[0] = this.makeStyleTag(this._name);\n this._optimizeForSpeed = \"insertRule\" in this.getSheet();\n if (!this._optimizeForSpeed) {\n if (!isProd) {\n console.warn(\"StyleSheet: optimizeForSpeed mode not supported falling back to standard mode.\");\n }\n this.flush();\n this._injected = true;\n }\n return;\n }\n this._serverSheet = {\n cssRules: [],\n insertRule: function(rule, index) {\n if (typeof index === \"number\") {\n _this._serverSheet.cssRules[index] = {\n cssText: rule\n };\n } else {\n _this._serverSheet.cssRules.push({\n cssText: rule\n });\n }\n return index;\n },\n deleteRule: function(index) {\n _this._serverSheet.cssRules[index] = null;\n }\n };\n };\n _proto.getSheetForTag = function getSheetForTag(tag) {\n if (tag.sheet) {\n return tag.sheet;\n }\n // this weirdness brought to you by firefox\n for(var i = 0; i < document.styleSheets.length; i++){\n if (document.styleSheets[i].ownerNode === tag) {\n return document.styleSheets[i];\n }\n }\n };\n _proto.getSheet = function getSheet() {\n return this.getSheetForTag(this._tags[this._tags.length - 1]);\n };\n _proto.insertRule = function insertRule(rule, index) {\n invariant$1(isString(rule), \"`insertRule` accepts only strings\");\n if (typeof window === \"undefined\") {\n if (typeof index !== \"number\") {\n index = this._serverSheet.cssRules.length;\n }\n this._serverSheet.insertRule(rule, index);\n return this._rulesCount++;\n }\n if (this._optimizeForSpeed) {\n var sheet = this.getSheet();\n if (typeof index !== \"number\") {\n index = sheet.cssRules.length;\n }\n // this weirdness for perf, and chrome's weird bug\n // https://stackoverflow.com/questions/20007992/chrome-suddenly-stopped-accepting-insertrule\n try {\n sheet.insertRule(rule, index);\n } catch (error) {\n if (!isProd) {\n console.warn(\"StyleSheet: illegal rule: \\n\\n\" + rule + \"\\n\\nSee https://stackoverflow.com/q/20007992 for more info\");\n }\n return -1;\n }\n } else {\n var insertionPoint = this._tags[index];\n this._tags.push(this.makeStyleTag(this._name, rule, insertionPoint));\n }\n return this._rulesCount++;\n };\n _proto.replaceRule = function replaceRule(index, rule) {\n if (this._optimizeForSpeed || typeof window === \"undefined\") {\n var sheet = typeof window !== \"undefined\" ? this.getSheet() : this._serverSheet;\n if (!rule.trim()) {\n rule = this._deletedRulePlaceholder;\n }\n if (!sheet.cssRules[index]) {\n // @TBD Should we throw an error?\n return index;\n }\n sheet.deleteRule(index);\n try {\n sheet.insertRule(rule, index);\n } catch (error) {\n if (!isProd) {\n console.warn(\"StyleSheet: illegal rule: \\n\\n\" + rule + \"\\n\\nSee https://stackoverflow.com/q/20007992 for more info\");\n }\n // In order to preserve the indices we insert a deleteRulePlaceholder\n sheet.insertRule(this._deletedRulePlaceholder, index);\n }\n } else {\n var tag = this._tags[index];\n invariant$1(tag, \"old rule at index `\" + index + \"` not found\");\n tag.textContent = rule;\n }\n return index;\n };\n _proto.deleteRule = function deleteRule(index) {\n if (typeof window === \"undefined\") {\n this._serverSheet.deleteRule(index);\n return;\n }\n if (this._optimizeForSpeed) {\n this.replaceRule(index, \"\");\n } else {\n var tag = this._tags[index];\n invariant$1(tag, \"rule at index `\" + index + \"` not found\");\n tag.parentNode.removeChild(tag);\n this._tags[index] = null;\n }\n };\n _proto.flush = function flush() {\n this._injected = false;\n this._rulesCount = 0;\n if (typeof window !== \"undefined\") {\n this._tags.forEach(function(tag) {\n return tag && tag.parentNode.removeChild(tag);\n });\n this._tags = [];\n } else {\n // simpler on server\n this._serverSheet.cssRules = [];\n }\n };\n _proto.cssRules = function cssRules() {\n var _this = this;\n if (typeof window === \"undefined\") {\n return this._serverSheet.cssRules;\n }\n return this._tags.reduce(function(rules, tag) {\n if (tag) {\n rules = rules.concat(Array.prototype.map.call(_this.getSheetForTag(tag).cssRules, function(rule) {\n return rule.cssText === _this._deletedRulePlaceholder ? null : rule;\n }));\n } else {\n rules.push(null);\n }\n return rules;\n }, []);\n };\n _proto.makeStyleTag = function makeStyleTag(name, cssString, relativeToTag) {\n if (cssString) {\n invariant$1(isString(cssString), \"makeStyleTag accepts only strings as second parameter\");\n }\n var tag = document.createElement(\"style\");\n if (this._nonce) tag.setAttribute(\"nonce\", this._nonce);\n tag.type = \"text/css\";\n tag.setAttribute(\"data-\" + name, \"\");\n if (cssString) {\n tag.appendChild(document.createTextNode(cssString));\n }\n var head = document.head || document.getElementsByTagName(\"head\")[0];\n if (relativeToTag) {\n head.insertBefore(tag, relativeToTag);\n } else {\n head.appendChild(tag);\n }\n return tag;\n };\n _createClass(StyleSheet, [\n {\n key: \"length\",\n get: function get() {\n return this._rulesCount;\n }\n }\n ]);\n return StyleSheet;\n}();\nfunction invariant$1(condition, message) {\n if (!condition) {\n throw new Error(\"StyleSheet: \" + message + \".\");\n }\n}\n\nfunction hash(str) {\n var _$hash = 5381, i = str.length;\n while(i){\n _$hash = _$hash * 33 ^ str.charCodeAt(--i);\n }\n /* JavaScript does bitwise operations (like XOR, above) on 32-bit signed\n * integers. Since we want the results to be always positive, convert the\n * signed int to an unsigned by doing an unsigned bitshift. */ return _$hash >>> 0;\n}\nvar stringHash = hash;\n\nvar sanitize = function(rule) {\n return rule.replace(/\\/style/gi, \"\\\\/style\");\n};\nvar cache = {};\n/**\n * computeId\n *\n * Compute and memoize a jsx id from a basedId and optionally props.\n */ function computeId(baseId, props) {\n if (!props) {\n return \"jsx-\" + baseId;\n }\n var propsToString = String(props);\n var key = baseId + propsToString;\n if (!cache[key]) {\n cache[key] = \"jsx-\" + stringHash(baseId + \"-\" + propsToString);\n }\n return cache[key];\n}\n/**\n * computeSelector\n *\n * Compute and memoize dynamic selectors.\n */ function computeSelector(id, css) {\n var selectoPlaceholderRegexp = /__jsx-style-dynamic-selector/g;\n // Sanitize SSR-ed CSS.\n // Client side code doesn't need to be sanitized since we use\n // document.createTextNode (dev) and the CSSOM api sheet.insertRule (prod).\n if (typeof window === \"undefined\") {\n css = sanitize(css);\n }\n var idcss = id + css;\n if (!cache[idcss]) {\n cache[idcss] = css.replace(selectoPlaceholderRegexp, id);\n }\n return cache[idcss];\n}\n\nfunction mapRulesToStyle(cssRules, options) {\n if (options === void 0) options = {};\n return cssRules.map(function(args) {\n var id = args[0];\n var css = args[1];\n return /*#__PURE__*/ React__default[\"default\"].createElement(\"style\", {\n id: \"__\" + id,\n // Avoid warnings upon render with a key\n key: \"__\" + id,\n nonce: options.nonce ? options.nonce : undefined,\n dangerouslySetInnerHTML: {\n __html: css\n }\n });\n });\n}\nvar StyleSheetRegistry = /*#__PURE__*/ function() {\n function StyleSheetRegistry(param) {\n var ref = param === void 0 ? {} : param, _styleSheet = ref.styleSheet, styleSheet = _styleSheet === void 0 ? null : _styleSheet, _optimizeForSpeed = ref.optimizeForSpeed, optimizeForSpeed = _optimizeForSpeed === void 0 ? false : _optimizeForSpeed;\n this._sheet = styleSheet || new StyleSheet({\n name: \"styled-jsx\",\n optimizeForSpeed: optimizeForSpeed\n });\n this._sheet.inject();\n if (styleSheet && typeof optimizeForSpeed === \"boolean\") {\n this._sheet.setOptimizeForSpeed(optimizeForSpeed);\n this._optimizeForSpeed = this._sheet.isOptimizeForSpeed();\n }\n this._fromServer = undefined;\n this._indices = {};\n this._instancesCounts = {};\n }\n var _proto = StyleSheetRegistry.prototype;\n _proto.add = function add(props) {\n var _this = this;\n if (undefined === this._optimizeForSpeed) {\n this._optimizeForSpeed = Array.isArray(props.children);\n this._sheet.setOptimizeForSpeed(this._optimizeForSpeed);\n this._optimizeForSpeed = this._sheet.isOptimizeForSpeed();\n }\n if (typeof window !== \"undefined\" && !this._fromServer) {\n this._fromServer = this.selectFromServer();\n this._instancesCounts = Object.keys(this._fromServer).reduce(function(acc, tagName) {\n acc[tagName] = 0;\n return acc;\n }, {});\n }\n var ref = this.getIdAndRules(props), styleId = ref.styleId, rules = ref.rules;\n // Deduping: just increase the instances count.\n if (styleId in this._instancesCounts) {\n this._instancesCounts[styleId] += 1;\n return;\n }\n var indices = rules.map(function(rule) {\n return _this._sheet.insertRule(rule);\n })// Filter out invalid rules\n .filter(function(index) {\n return index !== -1;\n });\n this._indices[styleId] = indices;\n this._instancesCounts[styleId] = 1;\n };\n _proto.remove = function remove(props) {\n var _this = this;\n var styleId = this.getIdAndRules(props).styleId;\n invariant(styleId in this._instancesCounts, \"styleId: `\" + styleId + \"` not found\");\n this._instancesCounts[styleId] -= 1;\n if (this._instancesCounts[styleId] < 1) {\n var tagFromServer = this._fromServer && this._fromServer[styleId];\n if (tagFromServer) {\n tagFromServer.parentNode.removeChild(tagFromServer);\n delete this._fromServer[styleId];\n } else {\n this._indices[styleId].forEach(function(index) {\n return _this._sheet.deleteRule(index);\n });\n delete this._indices[styleId];\n }\n delete this._instancesCounts[styleId];\n }\n };\n _proto.update = function update(props, nextProps) {\n this.add(nextProps);\n this.remove(props);\n };\n _proto.flush = function flush() {\n this._sheet.flush();\n this._sheet.inject();\n this._fromServer = undefined;\n this._indices = {};\n this._instancesCounts = {};\n };\n _proto.cssRules = function cssRules() {\n var _this = this;\n var fromServer = this._fromServer ? Object.keys(this._fromServer).map(function(styleId) {\n return [\n styleId,\n _this._fromServer[styleId]\n ];\n }) : [];\n var cssRules = this._sheet.cssRules();\n return fromServer.concat(Object.keys(this._indices).map(function(styleId) {\n return [\n styleId,\n _this._indices[styleId].map(function(index) {\n return cssRules[index].cssText;\n }).join(_this._optimizeForSpeed ? \"\" : \"\\n\")\n ];\n })// filter out empty rules\n .filter(function(rule) {\n return Boolean(rule[1]);\n }));\n };\n _proto.styles = function styles(options) {\n return mapRulesToStyle(this.cssRules(), options);\n };\n _proto.getIdAndRules = function getIdAndRules(props) {\n var css = props.children, dynamic = props.dynamic, id = props.id;\n if (dynamic) {\n var styleId = computeId(id, dynamic);\n return {\n styleId: styleId,\n rules: Array.isArray(css) ? css.map(function(rule) {\n return computeSelector(styleId, rule);\n }) : [\n computeSelector(styleId, css)\n ]\n };\n }\n return {\n styleId: computeId(id),\n rules: Array.isArray(css) ? css : [\n css\n ]\n };\n };\n /**\n * selectFromServer\n *\n * Collects style tags from the document with id __jsx-XXX\n */ _proto.selectFromServer = function selectFromServer() {\n var elements = Array.prototype.slice.call(document.querySelectorAll('[id^=\"__jsx-\"]'));\n return elements.reduce(function(acc, element) {\n var id = element.id.slice(2);\n acc[id] = element;\n return acc;\n }, {});\n };\n return StyleSheetRegistry;\n}();\nfunction invariant(condition, message) {\n if (!condition) {\n throw new Error(\"StyleSheetRegistry: \" + message + \".\");\n }\n}\nvar StyleSheetContext = /*#__PURE__*/ React.createContext(null);\nStyleSheetContext.displayName = \"StyleSheetContext\";\nfunction createStyleRegistry() {\n return new StyleSheetRegistry();\n}\nfunction StyleRegistry(param) {\n var configuredRegistry = param.registry, children = param.children;\n var rootRegistry = React.useContext(StyleSheetContext);\n var ref = React.useState(function() {\n return rootRegistry || configuredRegistry || createStyleRegistry();\n }), registry = ref[0];\n return /*#__PURE__*/ React__default[\"default\"].createElement(StyleSheetContext.Provider, {\n value: registry\n }, children);\n}\nfunction useStyleRegistry() {\n return React.useContext(StyleSheetContext);\n}\n\n// Opt-into the new `useInsertionEffect` API in React 18, fallback to `useLayoutEffect`.\n// https://github.com/reactwg/react-18/discussions/110\nvar useInsertionEffect = React__default[\"default\"].useInsertionEffect || React__default[\"default\"].useLayoutEffect;\nvar defaultRegistry = typeof window !== \"undefined\" ? createStyleRegistry() : undefined;\nfunction JSXStyle(props) {\n var registry = defaultRegistry ? defaultRegistry : useStyleRegistry();\n // If `registry` does not exist, we do nothing here.\n if (!registry) {\n return null;\n }\n if (typeof window === \"undefined\") {\n registry.add(props);\n return null;\n }\n useInsertionEffect(function() {\n registry.add(props);\n return function() {\n registry.remove(props);\n };\n // props.children can be string[], will be striped since id is identical\n }, [\n props.id,\n String(props.dynamic)\n ]);\n return null;\n}\nJSXStyle.dynamic = function(info) {\n return info.map(function(tagInfo) {\n var baseId = tagInfo[0];\n var props = tagInfo[1];\n return computeId(baseId, props);\n }).join(\" \");\n};\n\nexports.StyleRegistry = StyleRegistry;\nexports.createStyleRegistry = createStyleRegistry;\nexports.style = JSXStyle;\nexports.useStyleRegistry = useStyleRegistry;\n","module.exports = require('./dist/index').style\n","'use client';\n\nimport { useEffect, useState } from 'react';\nimport Link from 'next/link';\nimport { formatUptime, previewContent } from './components/utils';\nimport { StatsBar } from './components/StatsBar';\nimport { BroadcastBar } from './components/BroadcastBar';\nimport { TopoGraph } from './components/TopoGraph';\nimport { AgentCard } from './components/AgentCard';\nimport { InboxPanel } from './components/InboxPanel';\nimport { LoadingSkeleton } from './components/LoadingSkeleton';\nimport { NodesEmptyState as EmptyState } from './components/EmptyState';\nimport { AliasAvatar } from './components/AliasAvatar';\nimport { STATUS_DOT_HEX, STATUS_CHIP_CLASS } from './lib/status';\nimport { UserBar } from './components/UserBar';\nimport { CommandCenter, useCommandCenter } from './components/CommandCenter';\nimport { DispatchPanel } from './components/DispatchPanel';\nimport { useSessions, useHealth, useAnetConfig, useTasks, useStats } from './lib/hooks';\nimport { useSSE } from './lib/useSSE';\nimport { InboxMessage } from './components/types';\nimport { useSWRConfig } from 'swr';\n\nexport default function Dashboard() {\n // Auto-upgrade: if no V3 auth in session, force re-login to get user token\n useEffect(() => {\n const hasV3 = sessionStorage.getItem('anet_v3_auth');\n if (!hasV3) {\n // Try silent re-auth: logout old cookie + redirect to login\n fetch('/api/auth/logout', { method: 'POST' }).catch(() => {});\n window.location.assign('/login');\n }\n }, []);\n\n const { sessions, hint: sessHint, error: sessError, isLoading } = useSessions();\n const { health } = useHealth();\n const { config: anetConfig } = useAnetConfig();\n const { tasks } = useTasks({ limit: '500' });\n const { stats } = useStats();\n const [showTopo, setShowTopo] = useState(typeof window !== 'undefined' && window.innerWidth >= 1024);\n const [showConfig, setShowConfig] = useState(false);\n const cmd = useCommandCenter();\n const [showDispatch, setShowDispatch] = useState(false);\n const [inbox, setInbox] = useState<InboxMessage[]>([]);\n const [agentFilter, setAgentFilter] = useState<'all' | 'working' | 'idle' | 'offline'>('all');\n // #84: last node.renamed event — passed to TopoGraph so an open chat\n // popover follows the rename instead of pointing at a dead alias. `ts`\n // makes the effect re-fire even when the same from/to repeats.\n const [renameSignal, setRenameSignal] = useState<{ from: string; to: string; ts: number } | null>(null);\n const { mutate } = useSWRConfig();\n\n // SSE: instant revalidation on CommHub events\n // Opt-out via NEXT_PUBLIC_DISABLE_SSE=1 — avoids HTTP/1.1 head-of-line blocking on navigation\n const sseEnabled = process.env.NEXT_PUBLIC_DISABLE_SSE !== '1';\n const { connected: sseConnected, supported: sseSupported } = useSSE({\n url: '/api/hub/events',\n enabled: sseEnabled,\n onEvent: (event) => {\n // #84 (RFC-010 §3.4): node.renamed — revalidate the session list so the\n // new alias propagates instantly (TopoGraph + node grid re-render, the\n // avatar hue recomputes as a pure fn of alias) instead of waiting for\n // the next 5s poll. The renamed node's history keeps the old alias\n // server-side, so the task list needs no revalidation here.\n if (event.type === 'node.renamed') {\n mutate('/api/hub/status');\n const d = event.data as { old_alias?: string; new_alias?: string } | undefined;\n if (d?.old_alias && d?.new_alias) {\n setRenameSignal({ from: d.old_alias, to: d.new_alias, ts: Date.now() });\n }\n return;\n }\n if (['new_task', 'new_message', 'new_reply', 'node_status_changed', 'broadcast'].includes(event.type)) {\n mutate('/api/hub/status');\n mutate((key: string) => typeof key === 'string' && key.startsWith('/api/hub/tasks'), undefined, { revalidate: true });\n }\n },\n });\n\n // Fetch inbox (not in SWR since it accumulates)\n useEffect(() => {\n const fetchInbox = () => {\n fetch('/api/hub/inbox').then(r => r.json()).then(data => {\n if (data.messages?.length) setInbox(prev => {\n const ids = new Set(prev.map(m => m.id));\n const newMsgs = data.messages.filter((m: { id: string }) => !ids.has(m.id));\n return [...newMsgs, ...prev].slice(0, 100);\n });\n }).catch(() => {});\n };\n fetchInbox();\n const interval = setInterval(fetchInbox, 10000);\n return () => clearInterval(interval);\n }, []);\n\n if (isLoading) return <LoadingSkeleton />;\n\n const sseSessions = health?.sse_sessions || {};\n // SSE keys are `network_id:alias` since v0.7+ (per-network scoping).\n // Fall back to alias-only for legacy hubs.\n const sseLookup = (s: { alias: string; network_id?: string }) =>\n (s.network_id ? sseSessions[`${s.network_id}:${s.alias}`] : undefined) ?? sseSessions[s.alias];\n // Online = status is not 'offline' (not just SSE-connected)\n const isOnline = (s: { alias: string; status: string; network_id?: string }) => s.status !== 'offline' || !!sseLookup(s);\n const online = sessions.filter(isOnline).length;\n const total = sessions.length;\n const working = sessions.filter(s => s.status === 'working').length;\n const uptime = health ? formatUptime(health.uptime) : '--';\n const version = health?.version || '--';\n const configHealthy = Boolean(anetConfig?.hub && anetConfig.tokenConfigured);\n const configSourceLabel =\n anetConfig?.source === 'file' ? 'Local config'\n : anetConfig?.source === 'runtime-env' ? 'Runtime env'\n : 'Config missing';\n\n // Task stats: prefer /api/stats, fallback to manual\n const taskStats: Record<string, number> = {};\n if (stats?.tasks?.by_status?.length) {\n for (const s of stats.tasks.by_status) {\n taskStats[s.status] = s.count;\n }\n } else {\n for (const t of tasks) {\n taskStats[t.status] = (taskStats[t.status] || 0) + 1;\n }\n }\n\n const sortedSessions = [...sessions].sort((a, b) => {\n const aOnline = isOnline(a) ? 1 : 0;\n const bOnline = isOnline(b) ? 1 : 0;\n if (aOnline !== bOnline) return bOnline - aOnline;\n const aWorking = a.status === 'working' ? 1 : 0;\n const bWorking = b.status === 'working' ? 1 : 0;\n return bWorking - aWorking;\n });\n\n /** Round 70: when the fleet is empty, the Overview reorders to lead with\n * the \"Spin up your first agent\" CTA and hides the Quick Navigation /\n * Nav rail / Broadcast bar that would otherwise occupy prime real estate\n * with zeros and dead-end links. Computed once, used as a gate below. */\n const fleetEmpty = sessions.length === 0 && !sessError;\n\n return (\n <div className=\"min-h-screen bg-[#0a0a1a] text-gray-100 p-4 sm:p-6 font-mono\">\n <div className=\"lg:ml-0 ml-10\">\n <StatsBar online={online} working={working} total={total} version={version} uptime={uptime} />\n </div>\n\n {/* Dispatch + User Bar — Dispatch hidden when fleet empty (nothing to\n dispatch to); UserBar still useful (account/sign-out menu). */}\n <div className=\"flex items-center gap-3 mb-4\">\n {!fleetEmpty && (\n <button onClick={() => setShowDispatch(true)}\n className=\"px-4 py-2 bg-gradient-to-r from-cyan-600 to-blue-600 hover:from-cyan-500 hover:to-blue-500 text-white text-sm font-medium rounded-xl shadow-lg shadow-cyan-500/10 transition-all active:scale-95 shrink-0\">\n ⚡ Dispatch\n </button>\n )}\n <div className=\"flex-1\"><UserBar /></div>\n </div>\n\n {/* anet config (collapsed by default) */}\n <section className=\"mb-6 rounded-lg border border-[#2a2a4a] bg-[#111128] px-4 py-3 shadow-lg shadow-black/20\">\n <button onClick={() => setShowConfig(!showConfig)} className=\"w-full flex items-center justify-between text-left\">\n <div className=\"flex items-center gap-2 text-xs\">\n <span className=\"uppercase text-gray-600\">Config</span>\n <span className={`w-2 h-2 rounded-full ${configHealthy ? 'bg-green-400' : 'bg-red-400'}`} />\n <span className=\"text-gray-500\">{configSourceLabel}</span>\n </div>\n <svg className={`w-4 h-4 text-gray-600 transition-transform ${showConfig ? 'rotate-180' : ''}`} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M19 9l-7 7-7-7\" />\n </svg>\n </button>\n {showConfig && (\n <div className=\"mt-3 pt-3 border-t border-[#2a2a4a]\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"min-w-0\">\n <div className=\"text-gray-100 truncate text-sm\" title={anetConfig?.hub || undefined}>\n Hub: <span className={anetConfig?.hub ? 'text-cyan-300' : 'text-red-300'}>{anetConfig?.hub?.trim() || 'not configured'}</span>\n </div>\n </div>\n <div className=\"flex flex-wrap gap-2 text-xs\">\n <span className={`px-2.5 py-1 rounded-md border ${anetConfig?.tokenConfigured ? 'bg-blue-500/5 text-blue-300 border-blue-500/20' : 'bg-gray-500/5 text-gray-400 border-gray-500/20'}`}>\n Token: {anetConfig?.tokenPreview || 'not configured'}\n </span>\n </div>\n </div>\n {anetConfig?.error && <div className=\"mt-2 text-xs text-gray-600\">{anetConfig.error}</div>}\n </div>\n )}\n </section>\n\n {/* Task Status Stats */}\n {Object.keys(taskStats).length > 0 && (\n <section className=\"mb-6 rounded-lg border border-[#2a2a4a] bg-[#111128] px-4 py-3 shadow-lg shadow-black/20\">\n <div className=\"flex items-center justify-between mb-2\">\n <div className=\"text-xs uppercase text-gray-600\">Task Status</div>\n <Link href=\"/tasks\" prefetch={false} className=\"text-xs text-cyan-400 hover:text-cyan-300\">View all →</Link>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n {/* Round 69: order is \"hot first\" — active flow before terminal\n states — intentionally different from the lifecycle order on\n /tasks. Colors come from shared STATUS_CHIP_CLASS so a\n palette tweak in app/lib/status.ts updates here too.\n 'created' added in r69 (was missing — enum-coverage bug). */}\n {(['running', 'delivered', 'acked', 'replied', 'created', 'failed', 'cancelled', 'expired', 'closed'] as const)\n .filter((key) => taskStats[key])\n .map((key) => (\n <Link key={key} href={`/tasks?status=${key}`} prefetch={false} className={`px-2.5 py-1 rounded-md border text-xs ${STATUS_CHIP_CLASS[key]} hover:opacity-80 transition-opacity`}>\n {key}: {taskStats[key]}\n </Link>\n ))}\n </div>\n </section>\n )}\n\n {/* Quick Actions — split into two distinct intents:\n (1) Top: live stat cards (carry data, drill-in on click)\n (2) Bottom: pure nav rail (no number, icon + label)\n Round 24 — wrap both in a labelled block so the rhythm reads as\n \"here are the main jumps\" instead of two disconnected strips.\n Round 70 — entire Quick Nav + Nav rail + Broadcast + Recent\n Activity block is hidden when the fleet is empty so the\n onboarding CTA gets the page above the fold. */}\n {!fleetEmpty && <>\n <div className=\"text-[10px] uppercase tracking-[0.12em] text-gray-600 mb-2\">Quick navigation</div>\n <section className=\"mb-3 grid grid-cols-3 gap-2 sm:gap-3\">\n {(() => {\n // Build breakdown popover content per card. Pure data — pure CSS\n // popover (no JS state) wires the hover-show transition below.\n const idleCount = sessions.filter(s => isOnline(s) && s.status !== 'working').length;\n const offlineCount = total - online;\n const orderedStatuses = ['running', 'replied', 'failed', 'cancelled', 'expired', 'closed', 'created', 'delivered', 'acked'];\n const failedRecent = tasks.filter((t: { status: string }) => t.status === 'failed').length;\n\n const cards = [\n {\n href: '/nodes', label: 'Nodes',\n value: `${online}/${total}`,\n sub: `${total > 0 ? Math.round((online / total) * 100) : 0}% online`,\n color: 'text-green-400 border-green-500/20',\n popover: total === 0 ? null : [\n { k: 'working', v: working, dot: '', color: '#4ade80' },\n { k: 'idle', v: idleCount, dot: '', color: '#22d3ee' },\n { k: 'offline', v: offlineCount, dot: '', color: '#9ca3af' },\n ],\n },\n {\n href: '/tasks', label: 'Tasks',\n value: String(Object.values(taskStats).reduce((a, b) => a + b, 0) || 0),\n sub: 'all-time',\n color: 'text-cyan-400 border-cyan-500/20',\n popover: Object.keys(taskStats).length === 0 ? null\n : orderedStatuses\n .filter(s => taskStats[s])\n .map(s => {\n // Inline hex avoids Tailwind purging dynamic\n // `bg-${color}-400` class names.\n const hex = ({\n running: '#4ade80', replied: '#a78bfa', failed: '#f87171',\n cancelled: '#facc15', expired: '#fb923c', closed: '#9ca3af',\n created: '#9ca3af', delivered: '#60a5fa', acked: '#22d3ee',\n } as Record<string, string>)[s] || '#9ca3af';\n return { k: s, v: taskStats[s], dot: '', color: hex };\n }),\n },\n {\n href: '/tasks?status=failed', label: 'Failed',\n value: String(taskStats['failed'] || 0),\n sub: taskStats['failed'] ? 'needs review' : 'none',\n color: taskStats['failed'] ? 'text-red-400 border-red-500/25' : 'text-gray-500 border-gray-700/30',\n popover: !failedRecent ? [{ k: 'no failures yet', v: '', dot: '', color: '#4b5563' }]\n : [{ k: `${failedRecent} in current view`, v: '', dot: '', color: '#f87171' }],\n },\n ];\n\n return cards.map(a => (\n <Link\n key={a.href}\n href={a.href}\n prefetch={false}\n className={`anet-stat-link group relative rounded-xl border ${a.color} bg-[#111128] px-3 py-3 transition-all hover:-translate-y-px`}\n >\n <div className=\"flex items-baseline justify-between\">\n <div className={`text-xl font-semibold tabular-nums ${a.color.split(' ')[0]}`}>{a.value}</div>\n <div className=\"hidden sm:block text-[10px] text-gray-600 group-hover:text-gray-400 transition-colors\">View →</div>\n </div>\n <div className=\"text-[11px] text-gray-400 mt-0.5\">{a.label}</div>\n <div className=\"text-[10px] text-gray-600 mt-px\">{a.sub}</div>\n\n {/* Hover popover — CSS-only, restrained. Hidden on touch\n (no :hover) so mobile is unaffected. Positioned just\n below the card so it doesn't fight nav rail. */}\n {a.popover && a.popover.length > 0 && (\n <div className=\"anet-stat-popover hidden md:block pointer-events-none absolute left-2 right-2 top-full mt-1 z-20 rounded-lg border border-[#2a2a4a] bg-[#0d0d1a] shadow-lg shadow-black/30 px-3 py-2 opacity-0 translate-y-[-2px] group-hover:opacity-100 group-hover:translate-y-0 transition-all duration-150 delay-100\">\n <div className=\"text-[10px] text-gray-600 uppercase tracking-wider mb-1.5\">Breakdown</div>\n <ul className=\"space-y-1\">\n {a.popover.map(row => (\n <li key={row.k} className=\"flex items-center gap-2 text-[11px]\">\n <span\n className=\"inline-block w-1.5 h-1.5 rounded-full shrink-0\"\n style={{ backgroundColor: row.color }}\n aria-hidden\n />\n <span className=\"text-gray-400 capitalize\">{row.k}</span>\n {row.v !== '' && <span className=\"ml-auto text-gray-300 tabular-nums font-medium\">{row.v}</span>}\n </li>\n ))}\n </ul>\n </div>\n )}\n </Link>\n ));\n })()}\n </section>\n\n {/* Nav rail — pure navigation, icon + label, no data */}\n <section className=\"mb-6 grid grid-cols-3 gap-2 sm:gap-3\">\n {[\n { href: '/messages', label: 'Messages', icon: 'M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z' },\n { href: '/logs', label: 'Audit log', icon: 'M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z M14 2v6h6 M16 13H8 M16 17H8 M10 9H8' },\n { href: '/admin', label: 'Admin', icon: 'M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4z M6 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2' },\n ].map(a => (\n <Link key={a.href} href={a.href} prefetch={false}\n className=\"anet-nav-tile flex items-center justify-center gap-2 rounded-xl border border-[#2a2a4a] bg-[#111128] px-3 py-2.5 text-[12px] text-gray-400 hover:text-gray-200 transition-colors\">\n <svg className=\"w-4 h-4 shrink-0\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d={a.icon} />\n </svg>\n <span>{a.label}</span>\n </Link>\n ))}\n </section>\n\n <BroadcastBar />\n\n {/* Recent Activity */}\n {tasks.length > 0 && (\n <section className=\"mb-6 bg-[#111128] border border-[#2a2a4a] rounded-xl p-4\">\n <div className=\"flex items-center justify-between mb-3\">\n <h2 className=\"text-sm font-semibold text-gray-300\">Recent Activity</h2>\n <Link href=\"/tasks\" className=\"text-xs text-cyan-400 hover:text-cyan-300\">All tasks →</Link>\n </div>\n <div className=\"space-y-2 max-h-40 overflow-y-auto\">\n {tasks.slice(0, 5).map((t: { task_id: string; from_name: string; to_name: string; status: string; content: string; created_at: string }) => (\n <div key={t.task_id} className=\"flex items-center gap-2 text-xs\">\n <span\n className=\"w-1.5 h-1.5 rounded-full shrink-0\"\n style={{ backgroundColor: STATUS_DOT_HEX[t.status] || '#6b7280' }}\n />\n {t.from_name && <AliasAvatar alias={t.from_name} size={14} />}\n <span className=\"text-gray-300 shrink-0 max-w-[20%] truncate\">{t.from_name || '?'}</span>\n <span className=\"text-gray-600\">→</span>\n {t.to_name && <AliasAvatar alias={t.to_name} size={14} />}\n <span className=\"text-gray-300 shrink-0 max-w-[20%] truncate\">{t.to_name || '?'}</span>\n <span className=\"text-gray-500 truncate flex-1\" title={t.content || ''}>{previewContent(t.content).slice(0, 60)}</span>\n <span className={`shrink-0 px-1.5 py-0.5 rounded text-[10px] border ${\n STATUS_CHIP_CLASS[t.status] || 'text-gray-500 border-gray-700/30'\n }`}>{t.status}</span>\n </div>\n ))}\n </div>\n </section>\n )}\n </>}\n\n {sessError && (\n <div className=\"bg-red-900/20 border border-red-800/40 text-red-300 px-4 py-3 rounded-lg mb-6 text-sm flex items-center justify-between\" role=\"alert\">\n <span>{String(sessError)}</span>\n <span className=\"text-gray-500 text-xs\">Check CommHub connection</span>\n </div>\n )}\n\n {sessions.length > 0 && (\n <div className=\"flex justify-center mb-4\">\n <button\n onClick={() => setShowTopo(!showTopo)}\n className=\"text-xs text-gray-500 hover:text-gray-300 border border-gray-700/50 px-4 py-1.5 rounded-lg transition-colors hover:border-gray-600 cursor-pointer\"\n >\n {showTopo ? 'Hide Topology' : 'Show Topology'}\n </button>\n </div>\n )}\n\n {/* Mobile hint when topo hidden */}\n {!showTopo && sessions.length > 0 && (\n <div className=\"lg:hidden text-center text-xs text-gray-600 mb-4\">\n Topology hidden on mobile for better readability\n </div>\n )}\n\n {showTopo && sessions.length > 0 && <TopoGraph sessions={sessions} sseSessions={sseSessions} renameSignal={renameSignal} />}\n\n {sessions.length === 0 && !sessError ? (\n <EmptyState\n hint={sessHint}\n taskHistoryCount={Object.values(taskStats).reduce((a, b) => a + b, 0)}\n />\n ) : (() => {\n const counts = {\n all: sortedSessions.length,\n working: sortedSessions.filter(s => isOnline(s) && s.status === 'working').length,\n idle: sortedSessions.filter(s => isOnline(s) && s.status !== 'working').length,\n offline: sortedSessions.filter(s => !isOnline(s)).length,\n };\n const filtered = sortedSessions.filter(s => {\n if (agentFilter === 'all') return true;\n if (agentFilter === 'offline') return !isOnline(s);\n if (agentFilter === 'working') return isOnline(s) && s.status === 'working';\n if (agentFilter === 'idle') return isOnline(s) && s.status !== 'working';\n return true;\n });\n // Filter chip color keyed to status (round 34): working=green, idle=cyan,\n // offline=gray, all=neutral. Inline hex dots avoid Tailwind v4 purge.\n const chips: { key: typeof agentFilter; label: string; dot?: string }[] = [\n { key: 'all', label: 'All' },\n { key: 'working', label: 'Working', dot: '#4ade80' },\n { key: 'idle', label: 'Idle', dot: '#22d3ee' },\n { key: 'offline', label: 'Offline', dot: '#6b7280' },\n ];\n return (\n <>\n <div className=\"mb-3 flex flex-wrap items-center gap-1\">\n {chips.map(c => (\n <button\n key={c.key}\n type=\"button\"\n onClick={() => setAgentFilter(c.key)}\n disabled={counts[c.key] === 0 && c.key !== 'all'}\n className={`flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs transition-colors disabled:opacity-30 disabled:cursor-not-allowed ${\n agentFilter === c.key\n ? 'bg-cyan-500/10 text-cyan-300 border border-cyan-500/30'\n : 'text-gray-500 hover:text-gray-200 hover:bg-[#1a1a2a] border border-transparent'\n }`}\n >\n {c.dot && <span aria-hidden className=\"inline-block w-1.5 h-1.5 rounded-full\" style={{ backgroundColor: c.dot }} />}\n <span>{c.label}</span>\n <span className={`text-[10px] tabular-nums ${agentFilter === c.key ? 'text-cyan-400' : 'text-gray-600'}`}>{counts[c.key]}</span>\n </button>\n ))}\n </div>\n {/* Round 48: previous breakpoints had `lg:grid-cols-2 xl:grid-cols-3`\n which kept lg (1024-1279px) at only 2 columns even though\n each AgentCard is fine ≥260px wide. With the sidebar (208px),\n main area at lg is ~816px so 3 cols at ~272px each fits.\n xl breakpoint auto-inherits lg=3 cols; 2xl bumps to 4. */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 2xl:grid-cols-4 gap-3 sm:gap-4\">\n {filtered.map(s => (\n <AgentCard key={s.alias} session={s} hasSse={isOnline(s)} sseCount={sseLookup(s) || 0} onChat={cmd.openTab} />\n ))}\n </div>\n {filtered.length === 0 && (\n <div className=\"mt-4 text-center text-xs text-gray-600\">\n No agents match \"{agentFilter}\" — <button onClick={() => setAgentFilter('all')} className=\"underline hover:text-gray-400\">Show all</button>\n </div>\n )}\n </>\n );\n })()}\n\n <InboxPanel messages={inbox} />\n\n {/* Round 111 (issue #82): dropped the license badge — \"trial (12d\n left)\" read like a paywall countdown on an open-source dashboard\n and Vincent flagged it as misleading more than once. The SSE /\n polling dot stays: it's a real connection-status indicator, not\n a sales surface. */}\n <div className=\"mt-8 text-center text-xs text-gray-600 flex items-center justify-center gap-2 flex-wrap\">\n {sseSupported && (\n <>\n <span className={`w-1.5 h-1.5 rounded-full ${sseConnected ? 'bg-green-400' : 'bg-gray-600'}`} />\n {sseConnected ? 'SSE live' : 'SWR polling'}\n </>\n )}\n </div>\n\n {/* Dispatch Panel */}\n {showDispatch && <DispatchPanel sessions={sessions} onClose={() => setShowDispatch(false)} />}\n\n {/* Command Center (multi-tab chat) */}\n {cmd.tabs.length > 0 && (\n <CommandCenter\n tabs={cmd.tabs}\n activeTab={cmd.activeTab}\n onOpenTab={cmd.openTab}\n onCloseTab={cmd.closeTab}\n onSetActive={cmd.setActiveTab}\n onClose={cmd.closeAll}\n />\n )}\n </div>\n );\n}\n","'use client';\n\ninterface StatsBarProps {\n online: number;\n working: number;\n total: number;\n version: string;\n uptime: string;\n}\n\nexport function StatsBar({ online, working, total, version, uptime }: StatsBarProps) {\n const onlinePercent = total > 0 ? Math.round((online / total) * 100) : 0;\n const fleetEmpty = total === 0;\n\n return (\n <div className={fleetEmpty ? 'mb-4' : 'mb-8'}>\n {/* Title row */}\n <div className=\"flex flex-wrap items-center gap-3 mb-4\">\n <h1 className=\"text-2xl font-bold text-white tracking-tight\">Agent Network</h1>\n <span className=\"text-xs text-gray-500\">\n CommHub {version} · {uptime}\n </span>\n </div>\n\n {fleetEmpty ? (\n /* Round 72: thin status strip replaces the 4-card grid when fleet\n is empty. Saves ~280px on mobile (CTA y=650 → ~370) and keeps\n the same data visible in a single inline row. */\n <div className=\"anet-stat-strip flex flex-wrap items-center gap-x-4 gap-y-1.5 text-xs text-gray-500 border-t border-b border-[#2a2a4a] py-2\">\n <span className=\"inline-flex items-center gap-1.5\">\n <span aria-hidden className=\"inline-block w-1.5 h-1.5 rounded-full bg-gray-600\" />\n <span className=\"text-gray-300 tabular-nums\">0</span> online\n </span>\n <span className=\"text-gray-700\">·</span>\n <span className=\"inline-flex items-center gap-1.5\">\n <span aria-hidden className=\"inline-block w-1.5 h-1.5 rounded-full bg-gray-600\" />\n <span className=\"text-gray-300 tabular-nums\">0</span> working\n </span>\n <span className=\"text-gray-700\">·</span>\n <span className=\"inline-flex items-center gap-1.5\">\n <span aria-hidden className=\"inline-block w-1.5 h-1.5 rounded-full bg-gray-600\" />\n <span className=\"text-gray-300 tabular-nums\">0</span> registered\n </span>\n </div>\n ) : (\n /* Populated state — full 4-card grid as before */\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-3\">\n <StatCard\n value={online}\n label=\"Online\"\n sub={`${onlinePercent}% of fleet`}\n color=\"text-green-400\"\n accent=\"from-green-500/20 to-green-500/0\"\n border=\"border-green-500/15\"\n />\n <StatCard\n value={working}\n label=\"Working\"\n sub={online > 0 ? `${Math.round((working / online) * 100)}% utilization` : '--'}\n color=\"text-cyan-400\"\n accent=\"from-cyan-500/20 to-cyan-500/0\"\n border=\"border-cyan-500/15\"\n />\n <StatCard\n value={total - online}\n label=\"Offline\"\n sub={total - online === 0 ? 'All systems go' : `${total - online} disconnected`}\n color=\"text-gray-400\"\n accent=\"from-gray-500/10 to-gray-500/0\"\n border=\"border-gray-500/15\"\n />\n <StatCard\n value={total}\n label=\"Total\"\n sub=\"Registered nodes\"\n color=\"text-white\"\n accent=\"from-blue-500/15 to-blue-500/0\"\n border=\"border-blue-500/15\"\n />\n </div>\n )}\n </div>\n );\n}\n\nfunction StatCard({ value, label, sub, color, accent, border }: {\n value: number; label: string; sub: string; color: string; accent: string; border: string;\n}) {\n // Extract the color family (green/cyan/gray/blue/white) from `color` prop\n // so the light-theme top-strip CSS can pick the right accent.\n const accentKey = color.replace('text-', '').split('-')[0];\n return (\n <div\n data-anet-stat-card={accentKey}\n className={`anet-stat-card relative overflow-hidden rounded-xl border ${border} bg-[#111128] px-4 py-3 transition-all`}\n >\n <div className={`absolute inset-0 bg-gradient-to-br ${accent} pointer-events-none`} />\n <div className=\"relative\">\n <div className={`text-3xl font-bold ${color} tabular-nums leading-tight`}>{value}</div>\n <div className=\"text-sm text-gray-300 mt-0.5\">{label}</div>\n <div className=\"text-xs text-gray-600 mt-1\">{sub}</div>\n </div>\n </div>\n );\n}\n","'use client';\n\nimport { useState } from 'react';\n\nexport function BroadcastBar() {\n const [broadcastMsg, setBroadcastMsg] = useState('');\n const [broadcasting, setBroadcasting] = useState(false);\n const [broadcastResult, setBroadcastResult] = useState('');\n\n const sendBroadcast = async () => {\n if (!broadcastMsg.trim()) return;\n setBroadcasting(true);\n setBroadcastResult('');\n try {\n const res = await fetch('/api/hub/broadcast', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ message: broadcastMsg }),\n });\n const data = await res.json();\n if (data.ok) {\n setBroadcastResult(`Broadcast sent to ${data.recipients} node(s)`);\n setBroadcastMsg('');\n } else {\n setBroadcastResult(`Failed: ${data.error || 'Send error'}`);\n }\n } catch (e: unknown) {\n setBroadcastResult(`Failed: ${e instanceof Error ? e.message : 'Send error'}`);\n }\n setBroadcasting(false);\n setTimeout(() => setBroadcastResult(''), 5000);\n };\n\n return (\n <div className=\"mb-6\">\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={broadcastMsg}\n onChange={e => setBroadcastMsg(e.target.value)}\n onKeyDown={e => e.key === 'Enter' && sendBroadcast()}\n placeholder=\"Broadcast message to all online agents...\"\n maxLength={500}\n aria-label=\"Broadcast message\"\n className=\"flex-1 bg-[#111128] border border-[#2a2a4a] rounded-lg px-4 py-2.5 text-sm text-white placeholder-gray-600 focus:border-blue-500/50 focus:ring-1 focus:ring-blue-500/20 focus:outline-none transition-colors\"\n />\n <button\n onClick={sendBroadcast}\n disabled={broadcasting || !broadcastMsg.trim()}\n aria-label=\"Send broadcast\"\n className=\"flex items-center gap-2 px-5 py-2.5 bg-blue-600 hover:bg-blue-500 disabled:bg-gray-800 disabled:text-gray-600 text-white text-sm rounded-lg transition-all font-medium cursor-pointer disabled:cursor-not-allowed\"\n >\n {broadcasting ? (\n <>\n <svg aria-hidden className=\"w-4 h-4 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"3\" opacity=\"0.25\" />\n <path d=\"M4 12a8 8 0 018-8\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" />\n </svg>\n <span>Sending…</span>\n </>\n ) : (\n <>\n {/* Megaphone icon — \"broadcast to all\" affordance */}\n <svg aria-hidden className=\"w-4 h-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <path d=\"M3 11v3a1 1 0 0 0 1 1h2l3.29 3.29a1 1 0 0 0 1.71-.71V7.42a1 1 0 0 0-1.71-.71L6 10H4a1 1 0 0 0-1 1Z\" />\n <path d=\"M16 8a5 5 0 0 1 0 6\" opacity=\"0.7\" />\n <path d=\"M19 5a9 9 0 0 1 0 12\" opacity=\"0.45\" />\n </svg>\n <span>Broadcast</span>\n </>\n )}\n </button>\n </div>\n {broadcastMsg.length > 0 && (\n <div className=\"text-right text-xs text-gray-600 mt-1 tabular-nums\">{broadcastMsg.length}/500</div>\n )}\n {broadcastResult && (\n <div className={`mt-3 text-sm text-center anet-fade-in ${broadcastResult.startsWith('Failed') ? 'text-red-400' : 'text-green-400/80'}`}>\n {broadcastResult}\n </div>\n )}\n </div>\n );\n}\n","'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport useSWR from 'swr';\nimport { useRouter } from 'next/navigation';\nimport { Session } from './types';\nimport { aliasAvatarColors, aliasInitial } from './AliasAvatar';\nimport { ChatPopover } from './ChatPopover';\nimport { vendorForModel, runtimeIdentity, identityLine } from '../lib/vendorIdentity';\nimport { parseHubTime, relativeAgo } from '../lib/time';\n\n/** v0.10.0 Hero 1+2 / §3.F server-health hook — fetches the normalized\n * /api/hub/servers payload (preview.370 unblocked real-data via the\n * proxy schema-normalize layer) and exposes a per-hostname health\n * tier. red → worst-of-CPU/Mem/Disk ≥ 85%; amber → 60-85%; green\n * → < 60%; null when telemetry hasn't shipped yet OR the host is\n * offline.\n *\n * Shared with ServersDrawer via SWR's key-based dedup — both\n * consumers point at /api/hub/servers so the cache layer fans out\n * to a single fetch per refresh interval.\n */\ninterface ServerHealthRow {\n hostname: string;\n cpu_load_1min: number | null;\n cpu_cores: number;\n mem_used_gb: number | null;\n mem_total_gb: number | null;\n disk_used_gb: number | null;\n disk_total_gb: number | null;\n status: 'online' | 'offline';\n}\ntype ServerTier = 'red' | 'amber' | 'green';\nfunction classifyServer(s: ServerHealthRow): ServerTier | null {\n if (s.status === 'offline') return null;\n const cpuPct = s.cpu_load_1min != null && s.cpu_cores > 0 ? (s.cpu_load_1min / s.cpu_cores) * 100 : null;\n const memPct = s.mem_used_gb != null && s.mem_total_gb != null && s.mem_total_gb > 0 ? (s.mem_used_gb / s.mem_total_gb) * 100 : null;\n const diskPct = s.disk_used_gb != null && s.disk_total_gb != null && s.disk_total_gb > 0 ? (s.disk_used_gb / s.disk_total_gb) * 100 : null;\n const vals = [cpuPct, memPct, diskPct].filter((v): v is number => typeof v === 'number');\n if (vals.length === 0) return null;\n const worst = Math.max(...vals);\n if (worst >= 85) return 'red';\n if (worst >= 60) return 'amber';\n return 'green';\n}\nconst serversFetcher = async (url: string): Promise<{ servers: ServerHealthRow[] } | null> => {\n const res = await fetch(url);\n if (!res.ok) return null;\n return res.json();\n};\nfunction useServerHealthMap(): Map<string, ServerTier> {\n const { data } = useSWR<{ servers: ServerHealthRow[] } | null>(\n '/api/hub/servers',\n serversFetcher,\n { refreshInterval: 15000, dedupingInterval: 5000 },\n );\n return useMemo(() => {\n const m = new Map<string, ServerTier>();\n for (const s of data?.servers ?? []) {\n const tier = classifyServer(s);\n if (tier) m.set(s.hostname, tier);\n }\n return m;\n }, [data]);\n}\n\ninterface MessageFlow {\n from_alias: string;\n to_alias: string;\n content: string;\n created_at: string;\n}\n\ninterface TopoGraphProps {\n sessions: Session[];\n sseSessions: Record<string, number>;\n // #84: a node.renamed event from the Overview's SSE listener. When the\n // currently open chat popover targets `from`, it follows the rename to `to`.\n renameSignal?: { from: string; to: string; ts: number } | null;\n}\n\ninterface Point {\n x: number;\n y: number;\n}\n\ninterface FlowLink {\n key: string;\n from: string;\n to: string;\n count: number;\n content: string;\n /** ISO timestamp of the most recent message on this edge — drives\n * the Round 10 freshness fade so dormant links recede visually. */\n last_at: string;\n}\n\nconst cx = 500;\nconst cy = 330;\nconst onlineRadius = 220;\n// Round 97 (issue #50): tier into two rings when N > 8; round 98\n// (issue #61): tier into three rings when N > 14. At N=22 (Vincent's\n// network) two rings still leave 11 nodes per ring → inner chord of\n// 88px can't fit a 100px label; three rings give ~⌈N/3⌉ per ring so\n// every tier has enough arc room.\nconst onlineTierThreshold = 8;\nconst onlineTripleThreshold = 14;\nconst onlineInnerRadius = 175;\nconst onlineOuterRadius = 260;\nconst onlineTripleInnerR = 145;\nconst onlineTripleMidR = 215;\nconst onlineTripleOuterR = 285;\nconst offlineRadius = 325;\n\n/** Polar coordinate for a node on a ring. `rotateBy` lets the caller offset\n * the whole ring so two stacked rings don't align radially (round 25: the\n * offline ring gets a half-step rotation so its nodes sit in the gaps\n * between online ones). */\nfunction polarPoint(index: number, total: number, radius: number, rotateBy = 0) {\n const spread = total <= 2 ? Math.PI : Math.PI * 1.78;\n const start = -Math.PI / 2 - spread / 2;\n const angle = total <= 1 ? -Math.PI / 2 : start + (spread * index) / (total - 1) + rotateBy;\n return {\n x: cx + radius * Math.cos(angle),\n y: cy + radius * Math.sin(angle),\n };\n}\n\nfunction curvePath(from: Point, to: Point, lift = 0) {\n const mx = (from.x + to.x) / 2;\n const my = (from.y + to.y) / 2;\n const dx = to.x - from.x;\n const dy = to.y - from.y;\n const length = Math.hypot(dx, dy) || 1;\n const normalX = -dy / length;\n const normalY = dx / length;\n const control = {\n x: mx + normalX * lift,\n y: my + normalY * lift,\n };\n\n return `M${from.x},${from.y} Q${control.x},${control.y} ${to.x},${to.y}`;\n}\n\nfunction truncate(value: string, max: number) {\n return value.length > max ? `${value.slice(0, max - 1)}...` : value;\n}\n\n/** Round 46 / Loop: SWR-freshness chip.\n *\n * TopoGraph's data refreshes every 5 s via SWR but the user has no\n * visible signal that the canvas they're looking at is current. The\n * flow particles and chips (R42 active-links, R43 hub) tell you when\n * the FLEET last did something, not when the DATA last syncing. This\n * chip ticks `live · Ns` against a 1-second interval so the operator\n * can trust freshness without doing internal math.\n *\n * Owns its own setInterval so the parent topology doesn't re-render\n * every second (only this small chip does). lastSyncRef is updated\n * whenever the `sessions` prop reference changes — that's the SWR\n * refresh signal. */\nfunction FreshnessChip({ sessions }: { sessions: unknown }) {\n const lastSyncRef = useRef<number>(Date.now());\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n // sessions reference changed → SWR just delivered a new payload.\n lastSyncRef.current = Date.now();\n }, [sessions]);\n useEffect(() => {\n const id = setInterval(() => setNow(Date.now()), 1000);\n return () => clearInterval(id);\n }, []);\n const sec = Math.max(0, Math.floor((now - lastSyncRef.current) / 1000));\n // Tint the chip warmer when the data goes stale (>10s since last sync).\n // SWR's default refreshInterval here is 5s, so anything past ~10s is\n // either a poll miss or a network hiccup.\n const stale = sec > 10;\n // Round 187 / Loop: chip transitions between fresh (gray) and stale\n // (amber) colour palettes smoothly. Pre-R187 the className swap\n // snapped every time the stale boundary was crossed — could happen\n // multiple times per minute on a flaky network. Adding\n // transition-colors makes the stale-onset (gray → amber) and\n // recovery (amber → gray) ease through the bg / text / border\n // palette together. 300ms matches R161/R162 active-links chip\n // freshness fade timing for visual consistency in the chip row.\n // Round 315 / Loop: FreshnessChip joins the R313-R314 chip-row\n // data-weight family. When it appears (stale state only — R275\n // gated rendering to stale), it sits next to working/online/\n // active-links chips that all carry font-medium (R313) plus the\n // vendor letter chips (R314). Without font-medium the warning\n // chip would render at default 400 next to data chips at 500\n // — visual inconsistency right at the moment the chip exists to\n // grab attention. font-medium adds it to the HTML-context data\n // tier (R312-R314 family); the amber bg/text/border still does\n // the warning-state work, the weight just keeps the chip in the\n // same data-typography ladder as its siblings.\n const baseClass = \"hidden sm:inline px-2.5 py-1 rounded-md font-mono font-medium border transition-colors duration-300\";\n const colorClass = stale\n ? \"bg-amber-500/10 text-amber-300 border-amber-500/20\"\n : \"bg-gray-500/10 text-gray-400 border-gray-500/20\";\n /* Round 275 / Loop: simplification per Vincent 5214/5215-5217 visual\n audit (clutter cleanup for Twitter screenshot). Pre-R275 the chip\n ALWAYS rendered — \"live · 5s\" gray-on-gray at rest, \"lag · 15s\"\n amber when stale. The fresh state is an \"everything's fine\"\n affirmation that's implicit elsewhere on the canvas (counts\n updating, flows animating). Adding a permanent chip to the chip-\n row's right end for that affirmation is added visual chrome\n without proportional info value.\n\n R275 converts the chip to a CONDITIONAL warning indicator: render\n only when stale (sec > 10). Fresh state → null (chip absent). The\n amber stale chip still appears as a warning when SWR lags, so\n users see the problem signal; the fresh state implicitly relies\n on other liveness signals (recent-signal panel rows, edge\n animations, count updates).\n\n Net effect: chip-row at rest has 1 fewer chip (cleaner Twitter\n screenshot, less right-edge chrome), but signals appear on\n stale-onset to direct attention. */\n if (!stale) return null;\n return (\n <span\n className={`${baseClass} ${colorClass}`}\n title={stale ? `Last sync ${sec}s ago — SWR refresh may be lagging` : `Live data · refreshes every 5s · last sync ${sec}s ago`}\n data-freshness-chip\n data-freshness-chip-stale={stale ? 'true' : 'false'}\n >\n {/* Round 272 / Loop: swap prefix word to match color state so\n text and color point the same way. Pre-R272 the chip read\n \"live · {sec}s\" in BOTH fresh (gray) and stale (amber)\n states — the amber color signals \"concerning\" but \"live\"\n still says \"fresh data flowing\", a visual contradiction.\n Post-R272: fresh=\"live · {sec}s\" (gray + reassuring), stale=\n \"lag · {sec}s\" (amber + signals lagging). Same monospace\n cell count (3 chars + \" · \" + digits + \"s\") so no chip\n width jitter on threshold crossing; R187 transition-colors\n duration-300 still eases the bg/color flip. Title (hover\n tooltip) still spells out the full meaning in either\n state. */}\n {stale ? 'lag' : 'live'} · {sec}s\n </span>\n );\n}\n\n/** Round 36 / Loop: prefers-reduced-motion hook.\n *\n * Round 29's a11y sweep zeroed CSS animations via media query, but SVG\n * SMIL `<animate>` / `<animateMotion>` elements aren't reachable from CSS\n * — they need JS to opt out. This hook reads the media query and listens\n * for changes so the topology's flow particles, pulses, click ripple\n * and hub breath honour the OS-level preference. */\nfunction useReducedMotion(): boolean {\n const [reduced, setReduced] = useState(false);\n useEffect(() => {\n if (typeof window === 'undefined' || !window.matchMedia) return;\n const mq = window.matchMedia('(prefers-reduced-motion: reduce)');\n setReduced(mq.matches);\n const onChange = (e: MediaQueryListEvent) => setReduced(e.matches);\n mq.addEventListener('change', onChange);\n return () => mq.removeEventListener('change', onChange);\n }, []);\n return reduced;\n}\n\n// Round 38 / Loop: parseHubTime + relativeAgo factored to app/lib/time.ts\n// — the Round 35 fix lives there now alongside the Round 37 mirror so\n// any future TZ-safe parse update happens in one place.\n\n/** Round 12 / Loop: status trio audit.\n *\n * Each (status × theme) cell returns a {primary, halo, text} trio. The trio\n * invariant — keep it when adding states or tweaking shades:\n *\n * light: primary = <hue>-600 halo = <hue>-100 * text = <hue>-800/900\n * dark: primary = <hue>-400/500 halo = <hue>-900 text = <hue>-100\n *\n * * offline.halo light deviates intentionally to slate-200 (#e2e8f0):\n * slate-100 (#f1f5f9) is too close to the panel bg (#f8fafc) and the\n * halo would vanish. The other three rows keep the 100-shade.\n *\n * Audit caught one cross-family drift before this round: online-other halo\n * light was #dbeafe (blue-100) while its primary (#0284c7 sky-600) and text\n * (#0c4a6e sky-900) were sky-family — now sky-100 (#e0f2fe). Tiny visual\n * difference; large hygiene win — every trio is now mono-hue. */\nfunction nodeStatus(session: Session, isOnline: boolean, isLight: boolean) {\n if (!isOnline) {\n return {\n label: 'offline',\n primary: isLight ? '#94a3b8' : '#6b7280', // slate-400 / gray-500\n halo: isLight ? '#e2e8f0' : '#111827', // slate-200* / gray-900\n text: isLight ? '#475569' : '#9ca3af', // slate-600 / gray-400\n };\n }\n if (session.status === 'working') {\n return {\n label: 'working',\n primary: isLight ? '#059669' : '#22c55e', // emerald-600 / green-500\n halo: isLight ? '#d1fae5' : '#14532d', // emerald-100 / green-900\n text: isLight ? '#065f46' : '#dcfce7', // emerald-800 / green-100\n };\n }\n if (session.status === 'idle') {\n return {\n label: 'idle',\n primary: isLight ? '#0d9488' : '#2dd4bf', // teal-600 / teal-400\n halo: isLight ? '#ccfbf1' : '#134e4a', // teal-100 / teal-900\n text: isLight ? '#115e59' : '#ccfbf1', // teal-800 / teal-100\n };\n }\n return {\n label: session.status || 'online',\n primary: isLight ? '#0284c7' : '#38bdf8', // sky-600 / sky-400\n halo: isLight ? '#e0f2fe' : '#0c4a6e', // sky-100 / sky-900 (was blue-100 — drift fixed Round 12)\n text: isLight ? '#0c4a6e' : '#e0f2fe', // sky-900 / sky-100\n };\n}\n\n/** Theme-aware color palette for the topology SVG. */\ninterface Palette {\n panelStops: [string, string, string];\n radarStops: { color: string; opacity: number }[];\n arrowFill: string;\n ringStroke: string;\n spokeStroke: { active: string; idle: string };\n flowEdge: string;\n flowPath: string;\n flowParticle: string;\n nodeFill: { online: string; offline: string };\n labelBox: { fill: string; stroke: string };\n legendBox: { fill: string; stroke: string };\n legendText: string;\n legendHeadline: string;\n legendAccent: string;\n containerBg: string;\n containerBorder: string;\n topRailGradient: string;\n}\n\nconst DARK_PALETTE: Palette = {\n panelStops: ['#0b1220', '#080814', '#101018'],\n radarStops: [\n { color: '#22d3ee', opacity: 0.18 },\n { color: '#22c55e', opacity: 0.045 },\n { color: '#020617', opacity: 0 },\n ],\n arrowFill: '#67e8f9',\n ringStroke: '#164e63',\n spokeStroke: { active: '#22d3ee', idle: '#155e75' },\n flowEdge: '#67e8f9',\n flowPath: '#e0f2fe',\n flowParticle: '#fef08a',\n nodeFill: { online: '#020617', offline: '#080814' },\n labelBox: { fill: '#020617', stroke: '#1f2937' },\n legendBox: { fill: '#020617', stroke: '#1f2937' },\n legendText: '#94a3b8',\n legendHeadline: '#e5e7eb',\n legendAccent: '#67e8f9',\n containerBg: '#080814',\n containerBorder: '#2a2a4a',\n topRailGradient: 'from-transparent via-cyan-400/70 to-transparent',\n};\n\nconst LIGHT_PALETTE: Palette = {\n panelStops: ['#f8fafc', '#ffffff', '#f1f5f9'],\n radarStops: [\n { color: '#10b981', opacity: 0.06 },\n { color: '#3b82f6', opacity: 0.03 },\n { color: '#ffffff', opacity: 0 },\n ],\n arrowFill: '#10b981',\n ringStroke: '#cbd5e1',\n spokeStroke: { active: '#10b981', idle: '#cbd5e1' },\n flowEdge: '#10b981',\n flowPath: '#475569',\n flowParticle: '#f59e0b',\n nodeFill: { online: '#ffffff', offline: '#f8fafc' },\n labelBox: { fill: '#ffffff', stroke: '#e2e8f0' },\n legendBox: { fill: '#ffffff', stroke: '#e2e8f0' },\n legendText: '#475569',\n legendHeadline: '#0f172a',\n legendAccent: '#0d9488',\n containerBg: '#ffffff',\n containerBorder: '#e3e6eb',\n topRailGradient: 'from-transparent via-emerald-500/40 to-transparent',\n};\n\nfunction useTheme(): 'light' | 'dark' {\n const [theme, setTheme] = useState<'light' | 'dark'>('dark');\n useEffect(() => {\n const read = () => {\n const t = document.documentElement.getAttribute('data-theme') || 'cyber';\n setTheme(t === 'light' || t === 'mint' ? 'light' : 'dark');\n };\n read();\n const obs = new MutationObserver(read);\n obs.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });\n return () => obs.disconnect();\n }, []);\n return theme;\n}\n\n/** Round 100 (issue #79): brand showcase mode. Activate via `?brand=intern`\n * on any dashboard page — TopoGraph nodes show the 书小生 mascot instead\n * of alias initials. Stored in localStorage so the flag persists across\n * navigation. Clears with `?brand=none` or `?brand=` (empty). */\nfunction useBrand(): string | null {\n const [brand, setBrand] = useState<string | null>(null);\n useEffect(() => {\n try {\n const url = new URL(window.location.href);\n const param = url.searchParams.get('brand');\n if (param !== null) {\n if (param === '' || param === 'none') {\n localStorage.removeItem('anet-brand');\n setBrand(null);\n } else {\n localStorage.setItem('anet-brand', param);\n setBrand(param);\n }\n } else {\n setBrand(localStorage.getItem('anet-brand'));\n }\n } catch {}\n }, []);\n return brand;\n}\n\n/** Round 106 (issue #83): cluster agents by shared alias prefix so a team\n * reads as one unit in the topology. Adjacent (sorted) aliases that share\n * a ≥2-char prefix join the same group; the group key is the prefix common\n * to every member. Singletons map to their own alias (no hue shift). The\n * group key feeds aliasAvatarColors() so e.g. all 通信* nodes get one hue,\n * all 研究员* another — the \"同色相 tint\" clustering option from #83. */\nfunction commonPrefix(a: string, b: string): string {\n let i = 0;\n while (i < a.length && i < b.length && a[i] === b[i]) i++;\n return a.slice(0, i);\n}\n\n/** #83 + #111: group nodes that share a ≥2-char alias prefix OR a project_dir\n * (\"either criterion → same group\", Vincent 4724). Union-find over the\n * sessions; the component label prefers the shared project_dir's basename,\n * else the common alias prefix. Returns alias → groupKey. */\nfunction computeGroups(sessions: { alias: string; project_dir?: string | null }[]): Record<string, string> {\n const n = sessions.length;\n const parent = sessions.map((_, i) => i);\n const find = (i: number): number => {\n while (parent[i] !== i) { parent[i] = parent[parent[i]]; i = parent[i]; }\n return i;\n };\n const union = (a: number, b: number) => {\n const ra = find(a), rb = find(b);\n if (ra !== rb) parent[ra] = rb;\n };\n\n // union by shared project_dir\n const byDir: Record<string, number[]> = {};\n sessions.forEach((s, i) => {\n const d = s.project_dir?.trim();\n if (d) (byDir[d] ||= []).push(i);\n });\n for (const idxs of Object.values(byDir)) {\n for (let k = 1; k < idxs.length; k++) union(idxs[0], idxs[k]);\n }\n\n // union by shared ≥2-char alias prefix — sort, link adjacent pairs\n const order = sessions.map((_, i) => i).sort((a, b) => sessions[a].alias.localeCompare(sessions[b].alias));\n for (let k = 0; k + 1 < order.length; k++) {\n if (commonPrefix(sessions[order[k]].alias, sessions[order[k + 1]].alias).length >= 2) {\n union(order[k], order[k + 1]);\n }\n }\n\n // label each connected component\n const comps: Record<number, number[]> = {};\n for (let i = 0; i < n; i++) (comps[find(i)] ||= []).push(i);\n const keys: Record<string, string> = {};\n for (const members of Object.values(comps)) {\n let label: string;\n if (members.length === 1) {\n label = sessions[members[0]].alias;\n } else {\n const dirs = new Set(members.map(i => sessions[i].project_dir?.trim()).filter(Boolean) as string[]);\n if (dirs.size === 1) {\n const d = [...dirs][0];\n label = d.split('/').filter(Boolean).pop() || d;\n } else {\n label = members.map(i => sessions[i].alias).reduce((a, b) => commonPrefix(a, b));\n if (label.length < 2) label = sessions[members[0]].alias;\n }\n }\n for (const i of members) keys[sessions[i].alias] = label;\n }\n return keys;\n}\n\nfunction buildFlowLinks(messages: MessageFlow[], positions: Record<string, Point>) {\n const links = new Map<string, FlowLink>();\n\n messages.forEach(message => {\n if (\n !positions[message.from_alias] ||\n !positions[message.to_alias] ||\n message.from_alias === message.to_alias\n ) {\n return;\n }\n\n const key = `${message.from_alias}->${message.to_alias}`;\n const current = links.get(key);\n\n // Keep the most-recent timestamp per pair so the render can fade\n // dormant edges (Round 10 freshness fade).\n const incoming = message.created_at || '';\n const last_at = !current\n ? incoming\n : (incoming > current.last_at ? incoming : current.last_at);\n\n links.set(key, {\n key,\n from: message.from_alias,\n to: message.to_alias,\n count: (current?.count || 0) + 1,\n content: current?.content || message.content,\n last_at,\n });\n });\n\n return [...links.values()].slice(0, 18);\n}\n\nexport function TopoGraph({ sessions, sseSessions, renameSignal }: TopoGraphProps) {\n const theme = useTheme();\n const isLight = theme === 'light';\n const reducedMotion = useReducedMotion();\n const pal = isLight ? LIGHT_PALETTE : DARK_PALETTE;\n const brand = useBrand();\n const isIntern = brand === 'intern';\n // v0.10.0 Hero 1+2 / §3.F — per-host health tier map. Composes\n // with #119 ServersDrawer (same SWR key, deduped). When a node's\n // host server crosses into the red tier (CPU/Mem/Disk ≥ 85%),\n // the per-node SVG render adds a faint amber outer ring to flag\n // the issue without leaving the topology view.\n const hostHealthMap = useServerHealthMap();\n // R133: Next.js client-router for the recent-signal panel \"+N more\"\n // navigation. TopoGraph hasn't needed routing before — every other\n // affordance composes back into the canvas's own state — but the\n // truncated-flow footer is the one place where the user logically\n // wants to leave: \"show me the rest of the flows\" → /messages.\n const router = useRouter();\n const [messages, setMessages] = useState<MessageFlow[]>([]);\n // Issue #87: ring | grid layout toggle. Ring is the tiered-radial default;\n // grid arranges nodes in an N×M grid (better for 30+ nodes). Persisted to\n // localStorage like the zoom/pan view state. Declared above nodePositions\n // since that useMemo branches on it.\n const [layout, setLayout] = useState<'ring' | 'grid'>('ring');\n useEffect(() => {\n try {\n const saved = localStorage.getItem('anet-topo-layout');\n if (saved === 'grid' || saved === 'ring') {\n setLayout(saved);\n } else if (sessions.length >= 20) {\n // v0.10.0 Hero 3 Wave 1 §3.D — auto-grid for dense fleets.\n // When the user hasn't explicitly chosen a layout (no\n // localStorage entry), default to `grid` once the fleet\n // crosses 20 nodes. Below 20, the ring layout reads more\n // attractive (per #87 + R97-99 tier-ring history); at 20+\n // the ring starts packing tier 3 (R98 triple-tier\n // threshold) and grid scales cleaner — every cell visible\n // at the same density, no overlap-test risk from tier\n // crowding. The user's explicit toggle (R163 chrome\n // Ring|Grid) always wins by writing to anet-topo-layout,\n // so this only sets the *initial* preference for first-\n // time users on a dense fleet. Threshold 20 picked to\n // align with the existing density-aware breakpoints (R98\n // tier flip; R109 dense-label gating at 16). */}\n setLayout('grid');\n }\n } catch {}\n // sessions.length intentionally NOT in deps — this runs once on\n // mount and shouldn't re-fire when nodes join/leave (which would\n // override a user's mid-session toggle). The Ring|Grid chrome\n // button remains the authoritative source post-mount.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n // Round 170 / Loop: layout toggle (Ring ↔ Grid) used to teleport\n // every node from its old position to its new one in one paint\n // frame — the most jarring single user action left on the\n // canvas. Solution: dim the viewport <g> to ~45% opacity for\n // the duration of the swap so the eye reads it as a soft\n // crossfade-blink rather than a hard teleport. layoutSwitching\n // is a one-shot flag; the inline style on the viewport <g>\n // reads it and lerps opacity 1 → 0.45 → 1 across the swap.\n // Auto-clears after 400ms (covers fade-down 250ms + buffer for\n // React to commit the new layout's positions). Same pattern as\n // R168's smoothView arming but on a different visual axis\n // (opacity, not transform).\n const [layoutSwitching, setLayoutSwitching] = useState(false);\n const toggleLayout = () => {\n setLayoutSwitching(true);\n setTimeout(() => setLayoutSwitching(false), 400);\n setLayout(prev => {\n const next = prev === 'ring' ? 'grid' : 'ring';\n try { localStorage.setItem('anet-topo-layout', next); } catch {}\n return next;\n });\n };\n // Issue #113: node size scale (Vincent 4727 — nodes too big / crowded at\n // ~22 nodes). S/M/L → 0.7/0.84/1.0; default M (one notch down from the old\n // fixed size). Persisted like the layout toggle.\n const [nodeScale, setNodeScale] = useState(0.84);\n useEffect(() => {\n try {\n const saved = parseFloat(localStorage.getItem('anet-topo-nodescale') || '');\n if (saved === 0.7 || saved === 0.84 || saved === 1) setNodeScale(saved);\n } catch {}\n }, []);\n // Round 171 / Loop: nodeSize change picks up the R170 crossfade\n // pattern. Clicking S/M/L in the chrome re-derives every node's\n // radius + label sizing + (in grid layout) cell spacing — a\n // wholesale visual shift. Pre-R171 the resize snapped in one\n // paint frame; with this flag the viewport <g> opacity dims to\n // 0.45 for ~400ms, masking the redraw as a soft blink (same\n // vocabulary R170 uses for layout toggle). Bails early when\n // the picked scale matches the current one — clicking the\n // already-active button shouldn't fire a fade. Composes with\n // R170 layoutSwitching via `||` in the opacity expression; both\n // flags drive the same opacity transition but expose distinct\n // data-* attributes so tests can disambiguate which gesture\n // armed the crossfade.\n const [nodeSizeSwitching, setNodeSizeSwitching] = useState(false);\n const pickNodeScale = (v: number) => {\n if (v === nodeScale) return;\n setNodeSizeSwitching(true);\n setTimeout(() => setNodeSizeSwitching(false), 400);\n setNodeScale(v);\n try { localStorage.setItem('anet-topo-nodescale', String(v)); } catch {}\n };\n\n useEffect(() => {\n const fetchMessages = async () => {\n try {\n const res = await fetch('/api/hub/messages?limit=50');\n if (res.status === 401) {\n window.location.assign('/login');\n return;\n }\n\n const data = await res.json();\n setMessages(data.messages || []);\n } catch {}\n };\n fetchMessages();\n const interval = setInterval(fetchMessages, 5000);\n return () => clearInterval(interval);\n }, []);\n\n const {\n onlineNodes,\n offlineNodes,\n nodePositions,\n flowLinks,\n activeAliases,\n groupKeys,\n groupBoxes,\n gridContentBottom,\n } = useMemo(() => {\n const sseCount = (s: { alias: string; network_id?: string }) =>\n (s.network_id ? sseSessions[`${s.network_id}:${s.alias}`] : undefined) ?? sseSessions[s.alias];\n // Round 106 (issue #83): sort by alias so same-prefix agents\n // (通信龙 / 通信牛 / 通信工程马 …, or 研究员1号 / 研究员2号 …) end up\n // adjacent in the array — the tier layout below assigns angles by\n // index, so contiguous-in-array becomes contiguous-in-ring, i.e.\n // each team visually clusters. localeCompare keeps CJK ordering sane.\n const byAlias = (a: Session, b: Session) => a.alias.localeCompare(b.alias);\n // #112 umbrella: ghost age-out — an offline node not seen recently is\n // almost certainly a deleted node the server `/api/status` still\n // returns (#74 root cause is server-side; this is the dashboard-side\n // fallback so stale ghosts stop cluttering the topology). A missing\n // last_seen_at is kept (conservative — could be a legitimately new node).\n // Round 27 / P0: the 24h threshold was too lenient — Vincent's preview.29\n // screenshot showed B站马 nodes deleted ~4 h earlier still visible. A\n // healthy agent heartbeats every few seconds; if it's been silent for\n // an hour it's effectively gone. 1 h gives a fresh disconnect time to\n // come back while removing dead nodes well within an operator session.\n const GHOST_MS = 60 * 60 * 1000;\n const now = Date.now();\n const isGhost = (s: Session) => {\n if (s.status !== 'offline' || sseCount(s) || !s.last_seen_at) return false;\n // Round 35 / Loop: parseHubTime normalises SQL-style timestamps to UTC\n // before parsing so non-UTC browsers don't see a phantom 8-hour skew\n // and ghost freshly-disconnected nodes.\n const t = parseHubTime(s.last_seen_at);\n return t !== null && now - t > GHOST_MS;\n };\n const online = sessions.filter(s => s.status !== 'offline' || sseCount(s)).sort(byAlias);\n const offline = sessions.filter(s => s.status === 'offline' && !sseCount(s) && !isGhost(s)).sort(byAlias);\n const positions: Record<string, Point> = {};\n\n if (layout === 'grid') {\n // Issue #87 + #111: group-banded grid. Nodes are sorted by alias so\n // same-prefix aliases are adjacent; each multi-member prefix group then\n // gets its OWN row(s) starting at column 0, while singletons pack into\n // shared rows. Group-banded placement keeps every group's bounding box\n // (#111) a clean rectangle — no box ever overlaps another group's.\n const all = [...online, ...offline];\n const groupKeys = computeGroups(all);\n const cols = Math.max(1, Math.ceil(Math.sqrt(all.length)));\n // #112: gy0 starts below the (now compact) recent-signal / legend\n // overlay panels — their max bottom edge is y≈112 — so grid row 0 is\n // never occluded; gy1 extends near the canvas bottom for breathing room.\n const gx0 = 150, gx1 = 850, gy0 = 126, gy1 = 652;\n const cellW = (gx1 - gx0) / cols;\n // largest node radius at the current size scale — drives the group\n // label band + the row-height floor so nothing ever overlaps.\n const nodeR = Math.round(26 * nodeScale);\n\n // ordered runs of consecutive same-group-key nodes (≥2 = real group)\n const runs: { key: string; members: Session[] }[] = [];\n for (const s of all) {\n const gk = groupKeys[s.alias];\n const last = runs[runs.length - 1];\n if (last && last.key === gk) last.members.push(s);\n else runs.push({ key: gk, members: [s] });\n }\n\n // Pass 1 — assign each run to a band: a multi-member group owns its\n // rows (left-aligned, so its bounding box is a tidy rect); contiguous\n // singletons pack into shared rows (centred). Collect total row count.\n type Band = { members: Session[]; startRow: number; centred: boolean; isGroup: boolean };\n const bands: Band[] = [];\n let row = 0;\n let i = 0;\n while (i < runs.length) {\n if (runs[i].members.length >= 2) {\n bands.push({ members: runs[i].members, startRow: row, centred: false, isGroup: true });\n row += Math.ceil(runs[i].members.length / cols);\n i++;\n } else {\n const singles: Session[] = [];\n while (i < runs.length && runs[i].members.length < 2) {\n singles.push(runs[i].members[0]);\n i++;\n }\n bands.push({ members: singles, startRow: row, centred: true, isGroup: false });\n row += Math.ceil(singles.length / cols);\n }\n }\n const totalRows = Math.max(1, row);\n // #112: the group label sits in a band ABOVE the topmost node, so the\n // band must clear the node radius — GROUP_TOP is node-relative, never\n // cellH-derived (cellH-derived was the label↔node overlap bug Vincent\n // hit). cellH then floors at GROUP_TOP + 30 so the label band + bottom\n // padding always fit and stacked group boxes never touch; past the\n // floor the grid overflows and zoom/pan handles it.\n const GROUP_TOP = nodeR + 20;\n // Round 27 / P0 (Vincent screenshot preview.29): dense plain-text\n // node labels (`pos.y + radius + denseDrop`, with a 3 px containerBg\n // stroke halo for readability) at the BOTTOM row of band N would\n // paint OVER the start of band N+1's group label, creating the\n // \"blueleap → eleap\", \"agent-network-dashboard → t-network / board\"\n // visual chopping. Geometry of the collision:\n // dense label visual bottom = node_N.y + radius + denseDrop + halo\n // group label glyph top = boxY + 4 (text y=boxY+14, ~10px ascent)\n // = node_N+1.y - GROUP_TOP + 4\n // node_N+1.y - node_N.y = cellH for consecutive rows, so no-collide:\n // cellH ≥ radius + denseDrop + halo + GROUP_TOP - 4 + buffer\n // With halo=3 buffer=4: cellH ≥ nodeR + denseDrop + GROUP_TOP + 3.\n const denseDrop = nodeScale < 0.8 ? 12 : 14;\n const cellH = Math.max(\n 2 * nodeR + 22, // node + dense label within band\n GROUP_TOP + 12, // group-label band + box padding\n nodeR + denseDrop + GROUP_TOP + 3, // round-27 dense↔group label clearance\n Math.min(100, (gy1 - gy0) / totalRows),\n );\n\n // Pass 2 — place each band's members.\n for (const band of bands) {\n band.members.forEach((s, idx) => {\n const rowInBand = Math.floor(idx / cols);\n const c = idx % cols;\n const inRow = Math.min(cols, band.members.length - rowInBand * cols);\n const inset = band.centred ? ((cols - inRow) * cellW) / 2 : 0;\n positions[s.alias] = {\n x: gx0 + inset + (c + 0.5) * cellW,\n y: gy0 + (band.startRow + rowInBand + 0.5) * cellH,\n };\n });\n }\n\n const links = buildFlowLinks(messages, positions);\n const active = new Set<string>();\n links.forEach(link => { active.add(link.from); active.add(link.to); });\n // #111: one bounding box per multi-member group (Vincent 4722). Each\n // group owns its rows; GROUP_PAD fills the row space left below the\n // nodes after the label band, so stacked group boxes always have a\n // gap between them (GROUP_TOP is defined above, with cellH).\n const GROUP_PAD = Math.max(8, Math.min(26, cellH - GROUP_TOP - 8)); // side/bottom\n const groupBoxes = bands\n .filter(b => b.isGroup)\n .map(band => {\n const pts = band.members.map(s => positions[s.alias]).filter(Boolean);\n const xs = pts.map(p => p.x);\n const ys = pts.map(p => p.y);\n const minX = Math.min(...xs), minY = Math.min(...ys);\n // Round 58 / Loop: per-group status mix for the label pip strip.\n // Working = status==='working'. Idle = online but not working.\n // Offline = !isOnline (either status==='offline' AND no SSE, or\n // ghost-purged elsewhere — but ghosts never reach groupBoxes\n // since they're filtered out upstream). Counts feed the label\n // tspans directly so the strip stays inside the label's bbox,\n // preserving the node↔label overlap-test guarantee from R19.\n let w = 0, i = 0, o = 0;\n for (const s of band.members) {\n const isOn = s.status !== 'offline' || !!sseCount(s);\n if (s.status === 'working') w++;\n else if (isOn) i++;\n else o++;\n }\n return {\n key: band.members.length ? groupKeys[band.members[0].alias] : '',\n count: band.members.length,\n statuses: { working: w, idle: i, offline: o },\n x: minX - GROUP_PAD,\n y: minY - GROUP_TOP,\n w: Math.max(...xs) - minX + GROUP_PAD * 2,\n h: Math.max(...ys) - minY + GROUP_TOP + GROUP_PAD,\n };\n });\n // Round 28 / Loop: surface the grid's natural content bottom so the\n // mount effect can auto-fit zoom when the layout would overflow the\n // viewBox. = bottom of the last node row + its label drop + a small\n // breathing buffer. Round 27's cellH bump makes this overflow more\n // common (30-node fleets reach ~774 px, viewBox is 680).\n const gridContentBottom = gy0 + totalRows * cellH + 8;\n return {\n onlineNodes: online,\n offlineNodes: offline,\n nodePositions: positions,\n flowLinks: links,\n activeAliases: active,\n groupKeys,\n groupBoxes,\n gridContentBottom,\n };\n }\n\n // Round 97 (issue #50) + 98 (issue #61): three layout modes by N.\n // N ≤ 8 → single ring (r=220)\n // 8 < N ≤ 14 → two rings (inner r=175, outer r=260, half-step rot)\n // N > 14 → three rings (r=145/215/285, ⌈N/3⌉ per ring)\n // Each ring's spread is 1.78π so its node count drives chord length:\n // labels are 100px wide so each ring needs ≥110px chord per node.\n const tripleTier = online.length > onlineTripleThreshold;\n const dualTier = !tripleTier && online.length > onlineTierThreshold;\n let outerOnlineCount = online.length;\n if (tripleTier) {\n const per = Math.ceil(online.length / 3);\n const r1 = online.slice(0, per);\n const r2 = online.slice(per, 2 * per);\n const r3 = online.slice(2 * per);\n r1.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(r1.length, 1), onlineTripleInnerR);\n });\n const r1Spread = r1.length <= 2 ? Math.PI : Math.PI * 1.78;\n const r1Step = r1.length > 1 ? r1Spread / (r1.length - 1) : 0;\n r2.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(r2.length, 1), onlineTripleMidR, r1Step / 2);\n });\n r3.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(r3.length, 1), onlineTripleOuterR);\n });\n outerOnlineCount = r3.length;\n } else if (dualTier) {\n const innerCount = Math.ceil(online.length / 2);\n const outerCount = online.length - innerCount;\n const innerNodes = online.slice(0, innerCount);\n const outerNodes = online.slice(innerCount);\n innerNodes.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(innerCount, 1), onlineInnerRadius);\n });\n const innerSpread = innerCount <= 2 ? Math.PI : Math.PI * 1.78;\n const innerStep = innerCount > 1 ? innerSpread / (innerCount - 1) : 0;\n outerNodes.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(outerCount, 1), onlineOuterRadius, innerStep / 2);\n });\n outerOnlineCount = outerCount;\n } else {\n online.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(online.length, 1), onlineRadius);\n });\n }\n\n // Offset the offline ring radially by half the outermost online step so\n // offline bubbles sit in the angular gaps between online bubbles instead\n // of stacking directly behind them. Also push the outer ring further when\n // there are many offline nodes so labels don't crowd the legend.\n const outerSpreadBase = outerOnlineCount <= 2 ? Math.PI : Math.PI * 1.78;\n const outerStep = outerOnlineCount > 1 ? outerSpreadBase / (outerOnlineCount - 1) : 0;\n const offlineRotation = outerOnlineCount > 0 ? outerStep / 2 : 0;\n const offlineR = offlineRadius + Math.max(0, offline.length - 4) * 6;\n\n offline.forEach((s, index) => {\n positions[s.alias] = polarPoint(index, Math.max(offline.length, 1), offlineR, offlineRotation);\n });\n\n const links = buildFlowLinks(messages, positions);\n const active = new Set<string>();\n links.forEach(link => {\n active.add(link.from);\n active.add(link.to);\n });\n\n // Round 106 (issue #83): group key per alias → shared hue per team.\n const groupKeys = computeGroups([...online, ...offline]);\n\n return {\n onlineNodes: online,\n offlineNodes: offline,\n nodePositions: positions,\n flowLinks: links,\n activeAliases: active,\n groupKeys,\n // #111: group boxes are a grid-layout feature only — radially scattered\n // ring nodes can't be cleanly boxed. Ring keeps the #83 prefix hue.\n groupBoxes: [] as { key: string; count: number; statuses: { working: number; idle: number; offline: number }; x: number; y: number; w: number; h: number }[],\n // ring fits within VIEWBOX_H by construction (offlineRadius=325 + centre at y=330)\n gridContentBottom: 0,\n };\n }, [messages, sessions, sseSessions, layout, nodeScale]);\n\n const workingCount = onlineNodes.filter(s => s.status === 'working').length;\n // Round 6 / Loop: vendor distribution for the header chip — at a glance\n // \"what's in the fleet\" (A:5 M:2 O:8 书:12 …) without opening a node.\n // Sorted by count desc; \"unknown\" vendors collapse into a \"?\" bucket.\n const vendorDist = useMemo(() => {\n const tally = new Map<string, { initial: string; count: number; color: string }>();\n for (const s of [...onlineNodes, ...offlineNodes]) {\n const v = vendorForModel(s.model);\n const key = v.id === 'unknown' ? '?' : v.initial;\n const cur = tally.get(key);\n if (cur) cur.count++;\n else tally.set(key, { initial: key, count: 1, color: v.mono.text });\n }\n return [...tally.values()].sort((a, b) => b.count - a.count);\n }, [onlineNodes, offlineNodes]);\n // Round 109 (Vincent 4582 P0): hover-gated labels above this node count\n // so dense fleets show clean avatars instead of a wall of overlapping\n // label cards. 16 ≈ where the triple-tier rings start to crowd.\n const denseLayout = onlineNodes.length + offlineNodes.length > 16;\n const [hoveredAlias, setHoveredAlias] = useState<string | null>(null);\n // Round 86 / Loop: pointer-over-label preview. R63 wired the group\n // label to click-pin but hover gave no preview — the same hover/click\n // gap R83 closed for pressure-bar segments. This state lets the\n // label trigger the same dim mechanism a node hover already drives,\n // independently of whether the cursor happens to be over a member\n // node. ORs into hoveredGroup below so the existing derivation logic\n // (which composes with hoveredAlias, activeGroup, R85 marching ants)\n // keeps working unchanged.\n const [hoveredGroupLabel, setHoveredGroupLabel] = useState<string | null>(null);\n // Round 8 / Loop: which group is currently focused. Nodes outside this\n // group + other group boxes fade so the eye locks onto the team you're\n // pointing at. Singletons use their own alias as the group key.\n const hoveredGroup = hoveredGroupLabel\n ?? (hoveredAlias ? (groupKeys[hoveredAlias] ?? hoveredAlias) : null);\n // Round 63 / Loop: sticky group focus. R8 dims non-group nodes while\n // a member is hovered, but releasing the hover lets the focus fade.\n // Clicking the group label pins the group key here; activeGroup =\n // hoveredGroup ?? pinnedGroup so hover transiently overrides the\n // pin (handy for spot-comparing teams). Same compose pattern as\n // R60/R61 pinnedStatus.\n const [pinnedGroup, setPinnedGroup] = useState<string | null>(() => {\n // R66: same per-tab persistence treatment as pinnedStatus above.\n // The group key is opaque text (could be any prefix), so the init\n // reader can't validate it against a known enum — it just reads\n // whatever's stored. A useEffect below clears it if the value no\n // longer matches any current group (sessions changed since save).\n if (typeof window === 'undefined') return null;\n try { return sessionStorage.getItem('anet-topo-pinned-group'); } catch { return null; }\n });\n const activeGroup = hoveredGroup ?? pinnedGroup;\n // R66: sync pinnedGroup into sessionStorage when it changes; clear\n // it if it no longer matches any current group (the session set\n // can change between loads). The pinnedStatus sync sits next to\n // its declaration further down.\n useEffect(() => {\n try {\n if (pinnedGroup) sessionStorage.setItem('anet-topo-pinned-group', pinnedGroup);\n else sessionStorage.removeItem('anet-topo-pinned-group');\n } catch {}\n }, [pinnedGroup]);\n useEffect(() => {\n if (!pinnedGroup) return;\n const known = new Set(Object.values(groupKeys));\n if (!known.has(pinnedGroup)) setPinnedGroup(null);\n }, [pinnedGroup, groupKeys]);\n // Round 49 / Loop: reverse-direction of R40's edge-on-node-hover linkage.\n // R48 widened the flow hitbox to 16 px, so edges are precise enough to\n // serve as a state trigger. When the user hovers a flow edge, light up its\n // two endpoint nodes and dim the rest — \"who is this edge between\" becomes\n // visible without reading the tooltip. The set is the link's two aliases\n // (null when no edge hovered); node opacity composes this after inFocus.\n const [hoveredEdgeKey, setHoveredEdgeKey] = useState<string | null>(null);\n // Round 116 / Loop: sticky variant of hoveredEdgeKey. R56 made the\n // recent-signal rows brighten the matching edge on hover, but the\n // filter released on mouseleave — no way to lock a flow for a\n // closer look. Click-to-pin closes that gap, matching the\n // established hover→pin idiom (R60 status, R61 legend, R63 group,\n // R88 vendor). activeEdgeKey = hoveredEdgeKey ?? pinnedEdgeKey\n // below so hover still wins for spot-comparison while a pin is set.\n const [pinnedEdgeKey, setPinnedEdgeKey] = useState<string | null>(null);\n const activeEdgeKey = hoveredEdgeKey ?? pinnedEdgeKey;\n // Round 77 / Loop: hovering the \"N active links\" header chip globally\n // brightens every flow edge (1.5× opacity). Transient affordance — the\n // chip already says HOW MANY active flows exist; this answers WHERE\n // they are in one glance, without the user scanning the canvas for\n // moving particles. Reset on mouse leave.\n const [hoveredActiveLinks, setHoveredActiveLinks] = useState(false);\n // Round 115 / Loop: hub-center hover state. R52 made the hub\n // clickable (fit view) + cursor:pointer, but there was no\n // hover-time visual feedback — the click target felt guessed at\n // rather than confirmed. This state drives a subtle hint ring on\n // hover so users see the affordance before committing the click.\n const [hoveredHub, setHoveredHub] = useState(false);\n // R133: hover state for the recent-signal panel's \"+N more flows\"\n // navigation footer. Drives the on-hover opacity boost + underline\n // that signals interactivity, mirroring the hoveredHub idiom above.\n const [hoveredRecentMore, setHoveredRecentMore] = useState(false);\n // Round 346 / Loop: minimap-container hover affordance. The minimap\n // is a click-target (role=button at line ~7810, recenter-on-click +\n // Enter→resetView) but pre-R346 nothing visually changed on hover —\n // the only hint was the `cursor: crosshair` style. R346 lifts the\n // viewport rect (strokeWidth 1.5 → 1.75 + opacity 0.9 → 1.0) when\n // the user enters the minimap, marking \"this is the recenter target\n // and it's alive\". Sibling polish to the R332 minimap rounded-md →\n // rounded-lg corner family — that round refined geometry, this one\n // gives the viewport indicator inside the geometry a hover state.\n // 280ms ease-out transition list matches R199 smoothView vocabulary\n // so the visual joins the existing rhythm on the same rect.\n const [hoveredMinimap, setHoveredMinimap] = useState(false);\n // Round 347 / Loop: zoom-level readout hover-state letter-spacing\n // tween (0 → 0.5 px). The readout sandwiched between zoom-out /\n // zoom-in is a passive percent display — pre-R347 it had no hover\n // feedback at all (only a `title` tooltip). R347 extends the R344\n // (`+N more flows` footer) + R345 (panel titles) hover-letter-\n // spacing family from panel/footer surfaces into the HTML chrome\n // strip. Hovering the readout spreads its digits 0.5 px, signalling\n // \"this is alive\". tabular-nums + minWidth: 46 from R225 still lock\n // the column so the tween doesn't shove neighbouring controls.\n // 200ms ease-out joins the existing R264 color/border transition\n // list on the same span.\n const [hoveredZoomLevel, setHoveredZoomLevel] = useState(false);\n // R135: panel-wide hover-elevation. The recent-signal + legend\n // panels both already host clickable rows (R56/R116 recent rows,\n // R55/R61 legend rows) and a clickable footer (R133), so the\n // chrome itself is interactive territory. Drop-shadow boost on\n // mouseenter says \"this whole panel is alive\" — matches the R18\n // KPI-card-hover idiom from the Overview page. Single state for\n // both panels since they don't overlap; null when neither hovered.\n const [hoveredPanel, setHoveredPanel] = useState<'recent' | 'legend' | null>(null);\n // Round 80 / Loop: vendor-letter hover in the distribution chip. The\n // chip already names vendor mix (`C:5 G:3 ?:1`); hover a letter and\n // every node from OTHER vendors dims. Surfaces the breakdown spatially\n // without inventing a new pin slot. Stores the vendor `initial`\n // (single char or \"?\") that matches the tally key.\n const [hoveredVendor, setHoveredVendor] = useState<string | null>(null);\n // Round 88 / Loop: vendor filter pin. R80 added hover-to-dim on the\n // vendor letters but the filter released as soon as the cursor left\n // — vendor was the only filter dimension without a sticky variant\n // (R60 status, R63 group, R69 Cmd+K all support pin). This state\n // closes the gap. Same pattern as pinnedStatus / pinnedGroup:\n // per-tab sessionStorage persistence, Esc clears, activeVendor =\n // hoveredVendor ?? pinnedVendor so hover still wins for spot-\n // comparison while a pin is active.\n const [pinnedVendor, setPinnedVendor] = useState<string | null>(() => {\n if (typeof window === 'undefined') return null;\n try { return sessionStorage.getItem('anet-topo-pinned-vendor'); } catch { return null; }\n });\n const activeVendor = hoveredVendor ?? pinnedVendor;\n useEffect(() => {\n try {\n if (pinnedVendor) sessionStorage.setItem('anet-topo-pinned-vendor', pinnedVendor);\n else sessionStorage.removeItem('anet-topo-pinned-vendor');\n } catch {}\n }, [pinnedVendor]);\n // R89: stale-purge. If the fleet's vendor distribution changes such\n // that a previously-pinned vendor is gone (last node of that\n // vendor disconnected), clear the pin so the chip row doesn't\n // show \"filter: A · 0\" forever. Matches the same defensive purge\n // pattern pinnedGroup uses higher up — sessionStorage survives\n // reloads, but a stored value that no longer matches reality\n // would paint with an impossible filter.\n useEffect(() => {\n if (pinnedVendor && !vendorDist.some(v => v.initial === pinnedVendor)) {\n setPinnedVendor(null);\n }\n }, [pinnedVendor, vendorDist]);\n // Round 55 / Loop: hovering a legend status row dims nodes whose status\n // doesn't match. The legend was passive — \"what does this colour mean\".\n // Now it answers \"show me all of these\" the same way R8 group-focus\n // answers \"show me this team\". Three values match the legend rows.\n const [hoveredStatus, setHoveredStatus] = useState<'working' | 'idle' | 'offline' | null>(null);\n // Round 60 / Loop: sticky variant of `hoveredStatus`. R55 only filters\n // while the user is actively hovering the legend; for sweeping a fleet\n // you want to LOCK the filter. Each segment of the R31 pressure bar\n // toggles a pin — click again on the same segment to release. The node\n // opacity formula reads `activeStatus = hoveredStatus ?? pinnedStatus`\n // so hover transiently overrides a pin (handy for spot-comparison)\n // without nuking it.\n // Round 66 / Loop: pin survives a page reload via sessionStorage —\n // per-tab not per-browser (a new tab starts clean, intentionally).\n // The init reader validates against the known status set so a stale\n // / corrupt value can't paint the canvas with an impossible filter.\n const [pinnedStatus, setPinnedStatus] = useState<'working' | 'idle' | 'offline' | null>(() => {\n if (typeof window === 'undefined') return null;\n try {\n const v = sessionStorage.getItem('anet-topo-pinned-status');\n return (v === 'working' || v === 'idle' || v === 'offline') ? v : null;\n } catch { return null; }\n });\n const activeStatus = hoveredStatus ?? pinnedStatus;\n // R66: sync pinnedStatus into sessionStorage when it changes. Paired\n // with the matching effect for pinnedGroup higher up.\n useEffect(() => {\n try {\n if (pinnedStatus) sessionStorage.setItem('anet-topo-pinned-status', pinnedStatus);\n else sessionStorage.removeItem('anet-topo-pinned-status');\n } catch {}\n }, [pinnedStatus]);\n // R69: listen for Cmd+K palette pin actions. The palette can't reach\n // this component's state directly so it dispatches a CustomEvent;\n // we react by setting pinnedStatus / pinnedGroup. The palette also\n // writes to sessionStorage in lockstep, so the R66 init readers\n // would pick it up on reload — the event handler is what keeps the\n // currently-mounted canvas in sync without one.\n useEffect(() => {\n const onPin = (e: Event) => {\n const detail = (e as CustomEvent).detail || {};\n if (detail.kind === 'clear') {\n // R90: extend the universal clear to vendor. R69 only cleared\n // status + group because pinnedVendor didn't exist yet; R88\n // shipped the third pin and R90 closes the palette gap so\n // \"Clear topology filters\" really means all of them.\n // R117: extend again to pinnedEdgeKey — R116 added the 4th\n // pin dim and this command must clear it too or the edge stays\n // bright in the canvas while every other pill clears.\n setPinnedStatus(null);\n setPinnedGroup(null);\n setPinnedVendor(null);\n setPinnedEdgeKey(null);\n } else if (detail.kind === 'clear-vendor') {\n // R90: granular vendor-only clear so power users can release\n // just the vendor without disturbing status/group pins.\n setPinnedVendor(null);\n } else if (detail.kind === 'clear-edge') {\n // R117: granular edge-only clear, mirror of R90 clear-vendor.\n setPinnedEdgeKey(null);\n } else if (detail.kind === 'status') {\n const v = detail.value;\n if (v === 'working' || v === 'idle' || v === 'offline') setPinnedStatus(v);\n } else if (detail.kind === 'group' && typeof detail.value === 'string') {\n setPinnedGroup(detail.value);\n } else if (detail.kind === 'vendor' && typeof detail.value === 'string') {\n // R108: palette pin-vendor support. R69 added pin-status, R88\n // added clickable vendor letters in the chip row, R90 added\n // granular clear-vendor — but the palette never gained a\n // PIN-vendor command. This branch listens for it; the matching\n // commands live in CommandPalette R108. The stale-purge\n // useEffect higher up already drops a pin whose vendor isn't\n // in the current distribution, so commands for an absent\n // vendor are harmless.\n setPinnedVendor(detail.value);\n }\n };\n window.addEventListener('anet:topo-pin', onPin);\n return () => window.removeEventListener('anet:topo-pin', onPin);\n }, []);\n // R74 listener for layout + view palette commands lives below\n // fitView's declaration — see further down in the file.\n const hoveredEdgeEndpoints = useMemo<Set<string> | null>(() => {\n // R116: compose hover ?? pin so pinning a row via click keeps the\n // endpoint ring + edge ladder lit after mouseleave.\n if (!activeEdgeKey) return null;\n const link = flowLinks.find(l => l.key === activeEdgeKey);\n return link ? new Set([link.from, link.to]) : null;\n }, [activeEdgeKey, flowLinks]);\n\n // --- Round 103 (issue #81): fullscreen + zoom + pan interaction layer ---\n // DIY native (no d3 / svg-pan-zoom): wrap the topology content in a single\n // <g transform> and drive it with wheel + pointer-drag. The panel <rect>\n // backdrop stays fixed so panning never reveals empty canvas. View state\n // {zoom,x,y} persists to localStorage (same sticky pattern as brand flag).\n const VIEWBOX_W = 1000;\n const VIEWBOX_H = 680;\n const ZOOM_MIN = 0.5;\n const ZOOM_MAX = 4;\n const containerRef = useRef<HTMLDivElement>(null);\n const svgRef = useRef<SVGSVGElement>(null);\n const [view, setView] = useState({ zoom: 1, x: 0, y: 0 });\n const viewRef = useRef(view);\n const dragRef = useRef({ active: false, startX: 0, startY: 0, baseX: 0, baseY: 0 });\n const [isFullscreen, setIsFullscreen] = useState(false);\n // Round 184 / Loop: chrome reset button gets a one-shot icon spin\n // on click. resetSpinning is armed for 450ms (matches the CSS\n // animation duration in globals.css `.anet-reset-spin`); the SVG\n // icon picks up the class while armed. Same arming pattern as\n // R168/R169/R170/R171 smoothView flags but driving a CSS animation\n // instead of a CSS transition. prefers-reduced-motion blanket\n // override in R29 globals.css neutralises animation-duration\n // universally so the spin completes in 1ms when reduced motion is\n // requested.\n const [resetSpinning, setResetSpinning] = useState(false);\n const armResetSpin = () => {\n setResetSpinning(true);\n setTimeout(() => setResetSpinning(false), 460);\n };\n // Round 186 / Loop: chrome zoom-in / zoom-out buttons get a brief\n // icon pop on click — same click-feel idiom R184 added for the\n // reset spin, but a scale pulse rather than a rotation since +/−\n // icons rotating wouldn't read semantically. Tracks which button\n // is currently popping so only that icon picks up the class.\n // Arms for 240ms (CSS animation 220ms + 20ms buffer) so a quick\n // re-click can replay cleanly.\n // Round 249 / Loop: extend chromePopping to cover ring/grid layout\n // toggle + fullscreen button alongside the existing zoom-in/zoom-out.\n // Pre-R249 only the zoom buttons fired the R186 .anet-chrome-pop scale\n // pulse on click; the other chrome controls (layout toggle, fullscreen)\n // had no transient \"I just clicked\" signal — silent click → state change\n // with only the post-click visual difference to confirm action. R249\n // gives every clickable chrome control the same 220ms scale pulse, so\n // the whole strip speaks one consistent click vocabulary. Reset button\n // keeps its own R184 rotation animation (different gesture, semantic).\n // Node-size S/M/L keep their R171 layoutSwitching crossfade (already\n // gestural). Type union grows but the helper signature stays one-arg.\n type ChromePop = 'zoom-in' | 'zoom-out' | 'layout-ring' | 'layout-grid' | 'fullscreen' | 'size-S' | 'size-M' | 'size-L';\n const [chromePopping, setChromePopping] = useState<ChromePop | null>(null);\n const popChrome = (which: ChromePop) => {\n setChromePopping(which);\n setTimeout(() => setChromePopping(prev => prev === which ? null : prev), 240);\n };\n // Issue #100: singleton chat popover. One alias at a time — clicking\n // another node swaps the target and the conversation switches in place.\n const [chatAlias, setChatAlias] = useState<string | null>(null);\n // Round 14 / Loop: one-shot click ripple — fades outward from the clicked\n // node so the click registers physically before the popover snaps in.\n // Pairs with the Round 11 chat-focus ring: ripple expands, ring locks on.\n // Keyed by ts so re-clicking the same node remounts the <circle> and the\n // SMIL <animate> replays. Cleared 600ms after click (longer than the\n // 500ms animation so a final frame at opacity 0 is still in the tree).\n const [clickRipple, setClickRipple] = useState<{\n ts: number; x: number; y: number; r0: number; color: string;\n } | null>(null);\n // #84: when a node is renamed while its chat popover is open, follow the\n // rename so the conversation keeps targeting a live alias.\n useEffect(() => {\n if (renameSignal && renameSignal.from === chatAlias) {\n setChatAlias(renameSignal.to);\n }\n // chatAlias intentionally omitted — only react to a new rename signal,\n // not to the user opening/closing the popover.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [renameSignal]);\n\n useEffect(() => { viewRef.current = view; }, [view]);\n\n // restore persisted view once on mount\n useEffect(() => {\n try {\n const raw = localStorage.getItem('anet-topo-view');\n if (raw) {\n const v = JSON.parse(raw);\n if (typeof v?.zoom === 'number') {\n setView({\n zoom: Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, v.zoom)),\n x: typeof v.x === 'number' ? v.x : 0,\n y: typeof v.y === 'number' ? v.y : 0,\n });\n }\n }\n } catch {}\n }, []);\n\n // Round 28 / Loop: first-paint auto-fit. Round 27's cellH bump means\n // dense grids (≥6-7 rows) overflow the 680-px viewBox at the natural\n // 100% zoom — a new user sees the topology with its bottom rows\n // clipped and no hint that there's more below. Auto-fit on first\n // paint (and only if the user has no persisted view) sets the\n // initial zoom so all content fits. Subsequent zoom changes persist\n // and override the auto-fit on reload; explicit reset (0 key) still\n // goes back to 100% so the gesture's \"reset to natural size\" semantic\n // is preserved.\n //\n // Capture pre-mount persistence in a useState initializer — the\n // existing `persist` effect (declared below) runs on first render\n // with the default {1,0,0} view and writes it to localStorage before\n // the auto-fit effect (deps on async-arriving sessions) gets a turn.\n // Reading localStorage from the effect would see that write and\n // skip the fit. The useState snapshot fires once, before any effects.\n const [hadPersistedViewOnMount] = useState<boolean>(\n () => typeof window !== 'undefined' && !!localStorage.getItem('anet-topo-view'),\n );\n const autoFitDoneRef = useRef(false);\n useEffect(() => {\n if (autoFitDoneRef.current) return;\n if (hadPersistedViewOnMount) {\n autoFitDoneRef.current = true;\n return;\n }\n if (layout !== 'grid' || sessions.length === 0 || !gridContentBottom) return;\n if (gridContentBottom <= VIEWBOX_H) {\n autoFitDoneRef.current = true; // no overflow → no fit needed\n return;\n }\n const fitZoom = Math.max(ZOOM_MIN, Math.min(1, VIEWBOX_H / gridContentBottom));\n setView({ zoom: fitZoom, x: 0, y: 0 });\n autoFitDoneRef.current = true;\n }, [layout, sessions.length, gridContentBottom, hadPersistedViewOnMount]);\n\n // persist view\n useEffect(() => {\n try { localStorage.setItem('anet-topo-view', JSON.stringify(view)); } catch {}\n }, [view]);\n\n // track fullscreen state (button label + Esc-exit sync)\n useEffect(() => {\n const onFsChange = () => setIsFullscreen(document.fullscreenElement === containerRef.current);\n document.addEventListener('fullscreenchange', onFsChange);\n return () => document.removeEventListener('fullscreenchange', onFsChange);\n }, []);\n\n // wheel zoom — native non-passive listener so preventDefault() actually\n // stops the page from scrolling. Uses functional setState so the listener\n // never goes stale and can attach once.\n useEffect(() => {\n const svg = svgRef.current;\n if (!svg) return;\n const onWheel = (e: WheelEvent) => {\n // Round 23 / Loop: only zoom on Ctrl/Meta+wheel when inline; plain\n // wheel over the canvas keeps scrolling the page (the topo sits at\n // the top of /; trapping page scroll mid-page is the classic\n // \"scroll-jail\" anti-pattern). In fullscreen the canvas owns the\n // viewport so any wheel zooms — no page scroll to preserve. Pinch-\n // zoom on a trackpad surfaces as ctrlKey=true natively, which is\n // also what we want (zoom the canvas, not the browser).\n if (!isFullscreen && !e.ctrlKey && !e.metaKey) return;\n e.preventDefault();\n const rect = svg.getBoundingClientRect();\n const mx = ((e.clientX - rect.left) / rect.width) * VIEWBOX_W;\n const my = ((e.clientY - rect.top) / rect.height) * VIEWBOX_H;\n setView(prev => {\n // Round 104 (issue #81 follow-up): Vincent 实测 zoom 太灵敏. The\n // old factor was a fixed 1.15x per wheel *event* — fine for a\n // mouse notch but a trackpad fires dozens of events per gesture\n // so it compounded into huge jumps. Scale the factor by deltaY\n // magnitude with a small coefficient, then clamp the per-event\n // change so no single tick moves more than ~8%.\n const factor = Math.min(1.08, Math.max(0.926, Math.exp(-e.deltaY * 0.0006)));\n const nz = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, prev.zoom * factor));\n const ratio = nz / prev.zoom;\n // keep the point under the cursor stationary\n return { zoom: nz, x: mx - (mx - prev.x) * ratio, y: my - (my - prev.y) * ratio };\n });\n };\n svg.addEventListener('wheel', onWheel, { passive: false });\n return () => svg.removeEventListener('wheel', onWheel);\n // Re-attach when the fullscreen flag toggles so the handler's\n // closure picks up the new value (capture-by-closure means we'd\n // otherwise read the stale boolean forever).\n }, [isFullscreen]);\n\n // Round 21 / Loop: pan-aware cursor. Static `grab` gave no tactile cue\n // that the canvas was actually being dragged — \"grabbing\" while\n // dragRef.current.active = true tells the hand it's moving the\n // viewport. Mirroring dragRef.active into state is the only way to\n // re-render the SVG style; the ref itself doesn't trigger renders.\n const [isPanning, setIsPanning] = useState(false);\n const onPointerDown = (e: React.PointerEvent<SVGSVGElement>) => {\n if (e.button !== 0) return;\n (e.currentTarget as Element).setPointerCapture?.(e.pointerId);\n dragRef.current = {\n active: true,\n startX: e.clientX,\n startY: e.clientY,\n baseX: viewRef.current.x,\n baseY: viewRef.current.y,\n };\n setIsPanning(true);\n };\n const onPointerMove = (e: React.PointerEvent<SVGSVGElement>) => {\n const d = dragRef.current;\n if (!d.active) return;\n const svg = svgRef.current;\n if (!svg) return;\n const rect = svg.getBoundingClientRect();\n const dx = ((e.clientX - d.startX) / rect.width) * VIEWBOX_W;\n const dy = ((e.clientY - d.startY) / rect.height) * VIEWBOX_H;\n setView(prev => ({ ...prev, x: d.baseX + dx, y: d.baseY + dy }));\n };\n const onPointerUp = (e: React.PointerEvent<SVGSVGElement>) => {\n if (!dragRef.current.active) return;\n dragRef.current.active = false;\n setIsPanning(false);\n try {\n (e.currentTarget as Element).releasePointerCapture?.(e.pointerId);\n } catch {}\n };\n\n // zoom buttons — zoom around the canvas center\n const zoomBy = (factor: number) => {\n setView(prev => {\n const nz = Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, prev.zoom * factor));\n const ratio = nz / prev.zoom;\n const cx0 = VIEWBOX_W / 2;\n const cy0 = VIEWBOX_H / 2;\n return { zoom: nz, x: cx0 - (cx0 - prev.x) * ratio, y: cy0 - (cy0 - prev.y) * ratio };\n });\n };\n // Round 169 / Loop: discrete-zoom wrapper that arms the R168\n // smoothView flag before invoking zoomBy. Keyboard + / − and\n // the chrome zoom buttons fire once per gesture, so each\n // 1.2× step deserves the same 300ms glide R168 gives\n // reset/fit. Wheel zoom keeps calling zoomBy() directly —\n // every tick should respond live without lag. The arming\n // path is identical to resetView/fitView so all four\n // discrete-zoom surfaces share one timing constant.\n const zoomByDiscrete = (factor: number) => {\n setSmoothView(true);\n setTimeout(() => setSmoothView(false), 350);\n zoomBy(factor);\n };\n // Round 168 / Loop: smoothView is a one-shot flag that arms a\n // CSS transition on the viewport <g> transform attribute. Set\n // true when resetView/fitView fires; the inline style on the\n // viewport <g> reads this flag and conditionally applies\n // `transition: transform 300ms ease-out`. Auto-clears after\n // 350ms (just past the transition end) via setTimeout so\n // subsequent pan/wheel zoom stays snappy with no lag. Keeping\n // it as state (vs ref) so the React rerender re-applies the\n // style attribute synchronously when the transform also\n // changes — both attribute mutation and transition class\n // arrive in the same paint frame, which is what triggers the\n // browser to animate between the old and new transform values.\n const [smoothView, setSmoothView] = useState(false);\n const armSmoothView = () => {\n setSmoothView(true);\n setTimeout(() => setSmoothView(false), 350);\n };\n const resetView = () => {\n armSmoothView();\n setView({ zoom: 1, x: 0, y: 0 });\n };\n\n // Round 29 / Loop: `f` = fit-to-content. Shared by the Round 28\n // first-paint auto-fit effect and the keyboard handler so the math is\n // in one place. When content already fits at natural zoom, this is\n // effectively a \"recenter\" — `f` always lands on a known good view.\n // R168: arm smoothView so the transition glides instead of snapping\n // when the operator invokes fit-to-content via the hub click (R52),\n // chrome button, `f` key, or palette command.\n const fitView = useCallback(() => {\n const zoom = !gridContentBottom || gridContentBottom <= VIEWBOX_H\n ? 1\n : Math.max(ZOOM_MIN, Math.min(1, VIEWBOX_H / gridContentBottom));\n setSmoothView(true);\n setTimeout(() => setSmoothView(false), 350);\n setView({ zoom, x: 0, y: 0 });\n }, [gridContentBottom]);\n\n // R74: listen for layout + view palette commands. Sister to R69's\n // pin listener — palette dispatches a CustomEvent, the reducer here\n // calls toggleLayout / fitView. Sits below fitView's declaration so\n // the deps list resolves cleanly.\n useEffect(() => {\n const onLayout = (e: Event) => {\n const detail = (e as CustomEvent).detail || {};\n if (detail.kind === 'toggle') toggleLayout();\n };\n const onView = (e: Event) => {\n const detail = (e as CustomEvent).detail || {};\n if (detail.kind === 'fit') fitView();\n };\n window.addEventListener('anet:topo-layout', onLayout);\n window.addEventListener('anet:topo-view', onView);\n return () => {\n window.removeEventListener('anet:topo-layout', onLayout);\n window.removeEventListener('anet:topo-view', onView);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [fitView]);\n\n // Round 22 / Loop: keyboard zoom — +/= zoom in, - zoom out, 0 reset.\n // Round 29 / Loop: +f to fit content.\n // Listen on window so the user doesn't need to focus the SVG first,\n // but only act when no text input has focus (otherwise typing \"-\" in\n // a chat would zoom the topology — surprising and bad). Modifier keys\n // pass through so Cmd/Ctrl combos still hit their owners. The button\n // titles below document these shortcuts so users discover them.\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => {\n if (e.ctrlKey || e.metaKey || e.altKey) return;\n const ae = document.activeElement as HTMLElement | null;\n if (ae) {\n const tag = ae.tagName;\n if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || ae.isContentEditable) return;\n }\n if (e.key === '+' || e.key === '=') { zoomByDiscrete(1.2); e.preventDefault(); }\n else if (e.key === '-' || e.key === '_') { zoomByDiscrete(1 / 1.2); e.preventDefault(); }\n else if (e.key === '0') { resetView(); e.preventDefault(); }\n else if (e.key === 'f' || e.key === 'F') { fitView(); e.preventDefault(); }\n // Round 32 / Loop: `l` toggles ring|grid. The vim-style `g l` route\n // (Audit Log) requires a preceding `g` within 1500ms; a bare `l`\n // outside that window is free for topology use.\n else if (e.key === 'l' || e.key === 'L') { toggleLayout(); e.preventDefault(); }\n // Round 62 / Loop: Esc clears the R60/R61 pinned status filter so\n // users have a universal-cancel keyboard out. Esc on an open chat\n // is owned by ChatPopover (which only mounts when chatAlias is\n // set), so this handler is effectively scoped to \"no chat open\".\n // We additionally guard on chatAlias to be explicit — if the chat\n // closed mid-cycle, the pin can still be cleared on the next Esc.\n // R63: extends to pinnedGroup too. Clears WHATEVER pin is active\n // (one Esc collapses all topology pins) so the keyboard escape\n // route stays a single key, not \"Esc maybe Esc again\".\n else if (e.key === 'Escape' && !chatAlias && (pinnedStatus || pinnedGroup || pinnedVendor || pinnedEdgeKey)) {\n // R88/R116: extend the universal-cancel to vendor + edge too.\n // One Esc collapses every topology pin (matches R62/R63's\n // \"single key, not Esc-maybe-Esc-again\" promise).\n if (pinnedStatus) setPinnedStatus(null);\n if (pinnedGroup) setPinnedGroup(null);\n if (pinnedVendor) setPinnedVendor(null);\n if (pinnedEdgeKey) setPinnedEdgeKey(null);\n e.preventDefault();\n }\n };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n // zoomBy / resetView are stable wrt setView callback; fitView changes\n // with gridContentBottom so deps list catches it. R62 adds chatAlias\n // and pinnedStatus so the Escape branch reads fresh state (re-binding\n // the listener on these state changes is sub-ms — cheaper than refs).\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [fitView, chatAlias, pinnedStatus, pinnedGroup, pinnedVendor, pinnedEdgeKey]);\n\n const toggleFullscreen = () => {\n const el = containerRef.current;\n if (!el) return;\n if (document.fullscreenElement) {\n document.exitFullscreen?.();\n } else {\n const req =\n el.requestFullscreen ||\n (el as unknown as { webkitRequestFullscreen?: () => void }).webkitRequestFullscreen;\n req?.call(el);\n }\n };\n\n return (\n <section className=\"w-full max-w-6xl mx-auto mb-8\">\n {/* Round 299 / Loop: title block bottom margin mb-3 (12px) →\n mb-4 (16px). After R298 tightened the title-block internal\n gap (12→10px) packing brand-logo + kicker + h2 into a more\n cohesive editorial unit, the outer bottom margin to the\n topology canvas should breathe more — denser title block\n + tighter follow-on space read as cramped. Bumping the\n gap below the title block lets the canvas frame\n itself more clearly as the main visual subject. 16px is\n the conventional SaaS-product section-header-to-content\n baseline (Stripe / Linear / Vercel marketing). Geometry:\n adds 4 CSS px between title block bottom and topology\n frame top — small but cumulative with the R298 internal\n tighten, the title block reads as a *deliberate* badge\n rather than a casually-stacked label. */}\n {/* Round 334 / Loop: header outer wrapper mobile gap-3 → gap-2.5\n (12 px → 10 px). The wrapper is `flex flex-col` on narrow\n viewports (title-block above, chip-row below) — at mobile\n its vertical gap was 12 px while the title-block internal\n (R298) and chip-row internal (R328) both rhythm at 10 px.\n R334 unifies the OUTER vertical gap to 10 px so mobile\n stacked layout matches the established gap-rhythm tier\n (title-block 10 / chip-row 10 / chrome 8 — R298/R328/R326).\n Desktop `sm:flex-row` is unaffected: in row mode the gap-3\n would have applied horizontally but the wrapper relies on\n `sm:justify-between` for left/right anchoring (gap is then\n decorative only between the two flex-grow groups). Net\n mobile bump: 2 px tighter vertical breathing between\n title-block + chip-row. Geometry-safe — topo-overlap-test\n reads SVG-internal bbox, not header layout. */}\n <div\n className={`flex flex-col gap-2.5 sm:flex-row sm:items-end sm:justify-between mb-4 px-1${isFullscreen ? ' hidden' : ''}`}\n data-topo-header-row\n data-topo-header-hidden={isFullscreen ? 'true' : 'false'}\n >\n {/* Round 267 / Loop: title block adopts leading-tight on both\n kicker and h2 for a tighter editorial-style rhythm. Pre-\n R267 the kicker used Tailwind's compound `text-xs` (line-\n height 16px = 1.33 ratio) and the h2 used `text-lg` (line-\n height 28px = 1.56) — adequate but loose for a kicker→\n title sequence. R267 applies `leading-tight` (1.25) to\n both, shrinking effective line-heights to 15px + 22.5px =\n 37.5px total title block height (vs 44px pre-R267 → ~15%\n more compact) while preserving the cap-top to descender\n visual proportions. Result: kicker and title read as a\n single typographic unit rather than two loosely-stacked\n lines. data-topo-section-kicker / data-topo-section-title\n attrs make both probe-able. */}\n {/* P0 (Vincent 5222 / 通信龙 R278 dispatch): integrate sleep2agi\n brand logo into the title-block.\n Why HTML title-block instead of SVG canvas: the SVG region\n has a high-frequency concurrent-editor edit race with codex\n (see project_dashboard_concurrent_editors). The title block\n is HTML-side, low edit traffic, and gives the brand mark\n first-glance presence above the topology canvas — the exact\n \"Twitter screenshot 一眼看出 sleep2agi\" outcome Vincent 5215\n asked for.\n Logo construction: inline SVG so currentColor inherits\n from the parent text-{color} class — theme-aware without\n an extra asset request. Same crescent geometry as\n public/sleep2agi-logo.svg. 36×36 px so it's clearly\n readable at a16:9 Twitter crop, paired with the kicker +\n h2 typography via flex layout. text-cyan-300 in cyber +\n text-emerald-600 in light keeps the moon brand-aligned\n with the canvas accent palette. */}\n {/* Round 298 / Loop: title-block gap-3 (12px) → gap-2.5 (10px).\n The R297 codex-bundle brand-logo at 36×36 paired with the\n R285 tracking-widest kicker + R286 tracking-tight h2 forms\n an editorial \"logo + title\" unit. At gap-3 the 12px between\n logo right-edge and text left-edge reads as two separate\n elements — visual spacing slightly outpaces the relationship\n density (logo IS the brand mark for the kicker's\n \"Network Topology\"). gap-2.5 (10px) is the tight-pack\n convention SaaS-product header logos use (Stripe / Vercel /\n Linear top-nav logo + product name spacing), grouping logo\n + title as one read. Geometry: 36 + 10 + ~120 (title text\n width) = 166px total title-block width vs 168px pre-R298 —\n no measurable layout shift, just a deliberate tighter\n grouping. */}\n <div className=\"flex items-center gap-2.5\">\n {/* Round 297 / Loop: brand-logo color picks up the 200ms ease-\n out transition. Pre-R297 the moon glyph had theme-\n conditional color (cyber #67e8f9 cyan ↔ light #0d9488\n teal) but no transition declaration — flipping themes\n made the brand mark snap to its new color in one frame,\n jarring against the surrounding R245/R246/R247/R253/R254\n family that smooths every neighbouring fill / stroke /\n filter at the same 200ms cadence. Adding the transition\n brings the brand mark into the coordinated theme-toggle\n choreography: title block + canvas + chrome all ease as\n one unit. CSS color transition is well supported on the\n `color` property (which currentColor inside the masked\n <rect> inherits), so no SMIL trick needed. */}\n {/* Round 316 / Loop: brand-logo width/height 36 → 40 for\n slightly stronger first-glance presence. After R298\n tightened the title-block flex gap (12→10px) and R299\n widened the bottom margin (12→16px), the logo can hold\n more visual weight to balance the kicker+h2 stack on\n its right. 40px is 11% larger by edge, ~23% by area —\n visible bump on a Twitter-screenshot crop without\n overpowering the h2 at text-lg/font-semibold (R286).\n viewBox 32×32 unchanged so the inner crescent geometry\n scales proportionally. */}\n <svg\n width=\"40\" height=\"40\" viewBox=\"0 0 32 32\" aria-hidden\n className=\"shrink-0\"\n data-topo-brand-logo\n style={{\n color: isLight ? '#0d9488' : '#67e8f9',\n transition: 'color 200ms ease-out',\n }}\n >\n <mask id=\"s2a-titleblock-moon-mask\">\n <rect width=\"32\" height=\"32\" fill=\"black\" />\n <circle cx=\"16\" cy=\"16\" r=\"13\" fill=\"white\" />\n <circle cx=\"20.5\" cy=\"14.5\" r=\"11\" fill=\"black\" />\n </mask>\n <rect width=\"32\" height=\"32\" fill=\"currentColor\" mask=\"url(#s2a-titleblock-moon-mask)\" />\n </svg>\n <div>\n {/* Round 285 / Loop: kicker tracking-wider → tracking-widest.\n An uppercase eyebrow label at text-xs benefits from\n wider letter-spacing — Tailwind's tracking-widest is\n 0.1em vs tracking-wider's 0.05em. At small caps,\n 0.1em is the conventional SaaS-eyebrow spacing (Stripe,\n Linear, Vercel marketing kicker style); 0.05em reads\n closer to body-text density. The widened spacing\n telegraphs \"this is a label, not a sentence\" without\n changing color or size, deepening the editorial\n hierarchy R267 set up between kicker and h2. */}\n {/* Round 296 / Loop: kicker text-gray-600 → text-gray-500\n for slightly better legibility on the dark cyber backdrop.\n gray-600 (#4b5563) read as a near-invisible label on\n cyber (the canvas + side rail are deeply dark); gray-500\n (#6b7280) lifts the eyebrow into the band where the eye\n registers it as a deliberate label vs swallowed text,\n while still sitting clearly below text-white h2 title\n in the visual hierarchy. Tailwind classes are theme-\n neutral so the bump applies to both themes; in light\n theme gray-500 is still appropriate as a muted-label\n shade on white bg. Hierarchy preserved: title-white >\n kicker-gray-500 (R285 tracking-widest still in place). */}\n {/* Round 300 / Loop (milestone): kicker picks up font-medium\n (500). Pre-R300 the eyebrow used default font-weight\n (400/normal). At text-xs (12px) + uppercase + R285\n tracking-widest, default-weight letters read slightly\n under-authored — uppercase at small sizes wants a touch\n more stroke weight to feel like a deliberate label.\n font-medium (500) is the conventional SaaS-eyebrow\n weight (Stripe / Vercel / Linear marketing kicker\n style — same family that informed R285's tracking-\n widest decision). Stays clearly below the h2's font-\n semibold (600) + larger size so hierarchy is preserved:\n h2 (text-lg/600) > kicker (text-xs/500/gray-500).\n R300 marks the milestone of 25 rounds (R275-R300) of\n continuous TopoGraph polish + codex's Vincent 5215/\n 5222 logo asset+integration work. */}\n <div className=\"text-xs uppercase text-gray-500 tracking-widest leading-tight font-medium\" data-topo-section-kicker>Network Topology</div>\n {/* Round 286 / Loop: title 'Command mesh' adopts tracking-tight\n (-0.025em) to complement R285 kicker tracking-widest. Wide\n eyebrow + tight headline is the conventional editorial\n pairing — Apple / Stripe / Vercel / Linear all use this\n dual-axis typographic rhythm. The kicker's 0.1em pushes\n letters APART (label feel); the headline's -0.025em pulls\n them TOGETHER (deliberate, designed-headline feel). At\n text-lg (18px) the shift is ~0.45px per gap — small but\n cumulatively legible across 12 characters. font-semibold\n (600) stays — tracking-tight does the heavy lifting for\n the editorial register. */}\n <h2 className=\"text-lg text-white font-semibold leading-tight tracking-tight\" data-topo-section-title>Command mesh</h2>\n </div>\n </div>\n {/* Round 328 / Loop: chip-row strip wrapper gap 2 → 2.5\n (8px → 10px between chips). Pre-R328 the inter-chip gap\n sat at 8px while each chip's own horizontal padding was\n `px-2.5` (10px) — the chip's internal space was wider\n than the gap between chips, so adjacent chips read as\n \"touching\" rather than \"neighboring\". Bumping the gap\n to 10px makes inter-chip = chip-padding, visually\n balancing the rhythm. Sibling treatment to R298 title-\n block `gap-2.5` (brand-logo ↔ kicker/title) and R326\n chrome strip `gap-2` extension family. Layout strip:\n R298 title-block gap-2.5 (top of canvas)\n R328 chip-row gap-2.5 (below title) ← NEW\n R326 chrome gap-2 (bottom of canvas)\n Risk-bounded: chip-row uses `flex-wrap`; if it wraps to\n a new line on narrow viewports the row-gap also bumps to\n 10px, which only helps mobile rhythm. Topo-overlap-test\n is HTML-overlay-only at this scope; SVG viewBox layout\n untouched. */}\n <div className=\"flex flex-wrap items-center gap-2.5 text-xs\">\n {/* Issue #87: ring | grid layout toggle — segmented control,\n persisted to localStorage anet-topo-layout.\n Round 163 / Loop: bring the layout toggle into the R154\n chrome-button focus convention. Pre-R163 the buttons had\n aria-pressed + transition-colors but no focus-visible ring\n (browser default — invisible against dark canvas) and the\n inactive variant only nudged text from gray-500 → gray-400\n on hover with no bg tint, so the hover-to-click affordance\n was barely perceptible.\n\n R163 closes both gaps to match R154:\n focus-visible:ring-2 focus-visible:ring-cyan-400/60\n → keyboard users see exactly which segment is focused\n hover:bg-cyan-500/5 (inactive)\n → mouse hover shows a faint cyan ghost of the active\n state, signalling 'click to switch'\n hover:bg-cyan-500/20 (active)\n → active button responds too, says 'still clickable'\n data-topo-chrome-layout for testability symmetric with the\n R154 chrome buttons (data-topo-chrome-zoom-in / -reset /\n -fullscreen etc).\n\n Round 260 / Loop: chip-row semantic gap — Layout toggle is\n the only CONTROL in the chip row; everything that follows\n (working / online / pressure / vendor letters / active-\n links / filter pills / freshness) is READ-ONLY display.\n Pre-R260 all 8 children sat at uniform gap-2 (8px) — the\n spatial signal read as \"8 separate things\" instead of\n \"1 control + 7 display\". mr-1 (4px) on the Layout toggle\n stacks on top of the parent flex's gap-2 (8px) for an\n effective 12px gap before the first status chip — same\n law-of-proximity pattern R255 applied to the bottom-right\n chrome strip (fleet vs view groups). data-topo-chrome-\n layout-trailer marks the boundary surface for the gap\n probe. */}\n {/* Round 268 / Loop: Layout toggle border unified with the\n chrome strip's theme-aware borderColor. Pre-R268 the\n wrapper + Grid button's internal divider used hardcoded\n `border-gray-500/25` (pale gray, fixed in both themes)\n while the bottom-right chrome strip (nodeSize, zoom)\n used pal.containerBorder (cyber #2a2a4a dark indigo ↔\n light #e3e6eb pale gray). Visible mismatch in cyber\n theme: Layout toggle border read as pale gray while\n chrome strip borders read as darker indigo — two\n different border colors on visually-analogous\n segmented controls. R268 replaces the hardcoded class\n with inline pal.containerBorder + a border-color\n transition, so the Layout toggle (a) matches the chrome\n strip border color and (b) joins the canvas-wide\n theme-ease vocabulary (eases on cyber↔light toggle\n instead of snapping). Same change applied to the Grid\n button's border-l on line ~1493. */}\n {/* Round 329 / Loop: Layout toggle wrapper `mr-1` → `mr-0.5`\n to compensate for R328's chip-row gap bump (8 → 10 px).\n R260 designed for an effective 12 px gap between the\n Layout CONTROL and the first DISPLAY chip (working /\n online / etc): mr-1 (4 px) + chip-row gap-2 (8 px) = 12.\n R328 widened chip-row to gap-2.5 (10 px), pushing the\n effective gap to 14 px — semantically still \"control\n vs display\" but louder than R260 specified.\n R329 dials mr-1 → mr-0.5 (2 px) so the effective gap\n returns to 12 px (mr-0.5 + chip-row gap-2.5 = 2 + 10).\n Keeps the law-of-proximity semantic R260 designed\n while honoring R328's wider baseline rhythm. data-topo-\n chrome-layout-trailer attr unchanged — it still marks\n the boundary surface for the gap probe. */}\n <div\n className=\"mr-0.5 inline-flex rounded-md border overflow-hidden\"\n style={{ borderColor: pal.containerBorder, transition: 'border-color 200ms ease-out' }}\n role=\"group\"\n aria-label=\"Topology layout\"\n data-topo-chrome-layout-trailer\n >\n <button\n onClick={() => { popChrome('layout-ring'); if (layout !== 'ring') toggleLayout(); }}\n aria-pressed={layout === 'ring'}\n title=\"Ring layout (l to toggle)\"\n data-topo-chrome-layout=\"ring\"\n data-topo-chrome-layout-active={layout === 'ring' ? 'true' : 'false'}\n data-topo-chrome-layout-ring-popping={chromePopping === 'layout-ring' ? 'true' : 'false'}\n // Round 196 / Loop: add active: (pressed) state for tactile\n // click feedback — bridges mouse-down → R186/R184/R192 pop-on-\n // release. Selected variant deepens to cyan-500/25 (one tier\n // above its hover:cyan-500/20); unselected variant deepens\n // to cyan-500/15 (one tier above its hover:cyan-500/5).\n // Round 249 / Loop: chrome-pop joins the click handshake —\n // mouse-down deepens cyan press (R196), release fires\n // .anet-chrome-pop on the button (R249) AND triggers\n // toggleLayout if state changes. The pop runs even when\n // clicking the already-active layout (no state change),\n // confirming the click was received either way.\n /* Round 306 / Loop: focus-visible:ring-2 → ring-1 unifies\n with the rest of the chrome button family. Pre-R306\n the Layout toggle (Ring/Grid) used `focus-visible:\n ring-2` (2px outline) while nodeSize S/M/L (line\n ~7291), zoom -/+ (~7328/~7395), reset (~7417), and\n fullscreen (~7477) all use `focus-visible:ring-1`\n (1px outline). Two different focus-ring widths on\n visually-analogous chrome controls — same R268\n border-color unification + R288 icon-stroke\n unification family. Reducing Ring/Grid to ring-1\n lets all 7 chrome buttons share one focus-ring\n weight; cyan-400/60 + ring-inset retained. The\n R163/R196 hover/active deeps + R249 chrome-pop\n click feedback continue unchanged. */\n className={`px-2.5 py-1 transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset ${layout === 'ring' ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'text-gray-400 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15'} ${chromePopping === 'layout-ring' ? ' anet-chrome-pop' : ''}`}\n >\n Ring\n </button>\n <button\n onClick={() => { popChrome('layout-grid'); if (layout !== 'grid') toggleLayout(); }}\n aria-pressed={layout === 'grid'}\n title=\"Grid layout (l to toggle)\"\n data-topo-chrome-layout=\"grid\"\n data-topo-chrome-layout-active={layout === 'grid' ? 'true' : 'false'}\n data-topo-chrome-layout-grid-popping={chromePopping === 'layout-grid' ? 'true' : 'false'}\n // Round 196 / Loop: R163 layout-toggle Grid variant picks up\n // press-state — same tier pattern as Ring above.\n // Round 249 / Loop: chrome-pop on click — same as Ring.\n // Round 306 / Loop: focus-visible:ring-2 → ring-1 sibling\n // change to Ring above — unifies focus-ring width across\n // all chrome buttons.\n className={`px-2.5 py-1 border-l transition-colors focus:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset ${layout === 'grid' ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'text-gray-400 hover:text-cyan-300 hover:bg-cyan-500/5 active:bg-cyan-500/15'} ${chromePopping === 'layout-grid' ? ' anet-chrome-pop' : ''}`}\n /* Round 268 / Loop: Grid button's left border (the\n internal divider between Ring and Grid) picks up\n pal.containerBorder, matching the wrapper change at\n line ~1460 and the chrome strip's segmented borders\n (nodeSize, zoom). transition-colors className covers\n the border-color eased on theme toggle. */\n style={{ borderColor: pal.containerBorder }}\n >\n Grid\n </button>\n </div>\n {/* R79: working + online count chips become hover affordances —\n extends R77's chip-hover pattern to status counts. Hover the\n working chip → setHoveredStatus('working') so the R55 dim\n mechanic kicks in (same as hovering the SVG legend \"working\"\n row, just a different surface for the same gesture).\n The online chip uses the same setHoveredStatus('idle') path\n when there's idle but no working — falling back to working\n if any working node exists; \"online\" without a single bucket\n isn't part of the R55 type set, so this chip routes to the\n dominant online sub-tier instead of inventing a new state.\n Cursor only flips when there's anything to highlight. */}\n {/* R82: pin-mirror. R60 pressure-bar segments visualise the\n pinned status via an inset boxShadow; R61 legend rows via a\n concentric r=8 ring; the chip-row chips next to them did\n not — so a user who pinned via Cmd+K (R69) or the legend\n had no chip-row signal that \"working\" was the current\n filter. Mirror the pressure-bar treatment here so all\n status surfaces sing in unison. The online chip mirrors\n for idle pins (working ⊆ online; the routing in\n onMouseEnter already treats online as the idle fallback). */}\n {(() => {\n // R113 / Loop: extend the R97-R102/R101 alias-list tooltip\n // sweep to the remaining chip-row affordances. R79 made\n // these chips hoverable; their generic \"Hover to highlight\"\n // titles never said WHICH nodes match. Compute the alias\n // lists once for both chips to share.\n const workingAliases = onlineNodes.filter(s => s.status === 'working').map(s => s.alias);\n const onlineAliases = onlineNodes.map(s => s.alias);\n const truncate = (list: string[]) => {\n const head = list.slice(0, 8).join(', ');\n const tail = list.length > 8 ? ` + ${list.length - 8} more` : '';\n return head + tail;\n };\n const workingTitle = workingCount === 0\n ? undefined\n : pinnedStatus === 'working'\n ? `${truncate(workingAliases)} — pinned, Esc to clear`\n : `${truncate(workingAliases)} — hover highlights, click to pin`;\n // R140: online chip title gains a \"click to open /nodes\" tail\n // when interactive. R79 made the cursor pointer-shaped but\n // wired nothing; R136 + R139 closed the same lie on two\n // sibling chips by wiring real actions. The online chip\n // can't pin a single status (online = working + idle,\n // not a single pinnedStatus value), so a pin idiom would\n // be semantically wrong. /nodes is the natural full-list\n // destination — same \"click chip for the full list\" idiom\n // the active-links chip uses (R136 → /messages).\n const onlineTitle = onlineNodes.length === 0\n ? undefined\n : pinnedStatus === 'idle'\n ? `${truncate(onlineAliases)} — pinned, Esc to clear`\n : `${truncate(onlineAliases)} — hover highlights · click to open /nodes`;\n return (\n <>\n <span\n // Round 201 / Loop: the working chip joins the\n // \"chip's hover state deepens its OWN identity colour\"\n // family that R193 opened (active-links chip) and R195\n // extended (recent-panel footer). Pre-R201 hovering the\n // working chip fired R55 canvas dim + chip-row highlight\n // but the chip itself stayed at bg-green-500/10 — cause\n // silent, effect loud. R201 deepens its OWN green hue\n // (10→15 bg, 20→30 border) only when clickable.\n // transition-colors duration-200 blends the swap to\n // match R193's timing on the active-links chip.\n /* Round 232 / Loop: HTML chip row picks up the\n tabular-nums info-density treatment R224-R230\n established on the SVG side. The chip text reads\n \"{N} working\"; when workingCount crosses 9→10\n the leading digit's width shift propagates the\n trailing ' working' label right by the digit-vs-\n control glyph delta, and the chip's right edge\n re-flows because the parent is inline flex. The\n Tailwind `tabular-nums` utility sets font-variant-\n numeric: tabular-nums, locking the digit width\n so the chip text + chip width stay stable across\n all counter values. Sibling chips (online,\n active-links) get the same treatment in this\n round so the three-chip row reads uniformly.\n 7th surface in the info-density tabular-nums\n sweep — and the first on the HTML side\n (previous 6 were SVG <text>/<tspan>). */\n className={`tabular-nums font-medium px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-colors duration-200 ${\n workingCount > 0\n ? 'bg-green-500/10 text-green-300 border-green-500/20 hover:bg-green-500/15 hover:border-green-500/30'\n : 'bg-green-500/10 text-green-300 border-green-500/20'\n }`}\n data-working-chip\n data-working-chip-aliases={workingAliases.join(',')}\n data-pin-mirror={pinnedStatus === 'working' ? 'true' : 'false'}\n data-working-chip-clickable={workingCount > 0 ? 'true' : 'false'}\n data-working-chip-empty={workingCount === 0 ? 'true' : 'false'}\n title={workingTitle}\n role={workingCount > 0 ? 'button' : undefined}\n tabIndex={workingCount > 0 ? 0 : undefined}\n aria-pressed={workingCount > 0 ? (pinnedStatus === 'working') : undefined}\n // Round 180 / Loop: pin-mirror inset rings now ease in/out\n // instead of snapping. R165 added this transition to the\n // pressure-bar segments; R180 closes the smooth-pin-mirror\n // family across the three remaining chip-row pin chips\n // (working / online / vendor letter). The visual is small\n // — a 1-2 px inset double ring — but the eye catches the\n // pop on every pin/unpin without the ease.\n // Round 201 / Loop: inline transition list now also covers\n // background-color + border-color so the R201 hover tint\n // eases. Tailwind transition-colors on the className would\n // be overridden by this inline declaration, so we splice\n // the colour properties directly into the existing\n // R180 box-shadow transition.\n // Round 205 / Loop: chip recedes to opacity 0.5 when its\n // tier is empty (workingCount=0). Pre-R205 \"0 working\"\n // displayed at full bg-green-500/10 chrome — visually\n // indistinguishable from \"12 working\". Eye got zero\n // empty-tier signal. R205 mirrors R204's legend count\n // recede-on-empty pattern at the chip-row scope. Inline\n // transition list extends `opacity 200ms ease-out` so\n // the crossing-zero ease matches R201's bg/border\n // timing. Empty-state combines with R139's clickable=\n // false + tooltip-undefined: visual + interactive +\n // affordance all say \"this tier has nothing to act on\".\n style={{\n cursor: workingCount > 0 ? 'pointer' : undefined,\n opacity: workingCount === 0 ? 0.5 : 1,\n boxShadow: pinnedStatus === 'working' ? 'inset 0 0 0 1px #4ade80, inset 0 0 0 2px rgba(255,255,255,0.45)' : undefined,\n transition: 'box-shadow 150ms ease-out, background-color 200ms ease-out, border-color 200ms ease-out, opacity 200ms ease-out',\n }}\n onMouseEnter={() => { if (workingCount > 0) setHoveredStatus('working'); }}\n onMouseLeave={() => setHoveredStatus(prev => prev === 'working' ? null : prev)}\n // R139: the title hover-text has been promising \"click to\n // pin\" since R79 but no onClick was ever wired. The cursor:\n // pointer at line 1363 set up the same lie R136 fixed on\n // the active-links chip. Wire it now: click toggles the\n // status pin to 'working', composing with R60 (pressure-\n // bar segments) and R61 (legend rows) — three different\n // surfaces that all toggle the same pinnedStatus. boxShadow\n // pin-mirror at line 1364 already reflects the state; aria-\n // pressed now exposes it for screen readers too.\n onClick={() => {\n if (workingCount > 0) setPinnedStatus(prev => prev === 'working' ? null : 'working');\n }}\n onKeyDown={(e) => {\n if (workingCount === 0) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setPinnedStatus(prev => prev === 'working' ? null : 'working');\n }\n }}\n >\n {/* Round 337 / Loop: split working chip into digit +\n \" working\" unit, with the unit at opacity-70.\n Extends the R333/R335/R336 chip-internal-hierarchy\n arc from SVG (panel headers) and pin-chip prefix\n surfaces into HTML chip-row chips. Recurring\n pattern: small label spans demote, value stays\n prominent. data-working-chip-unit exposes the\n span for tests. */}\n {workingCount}<span className=\"opacity-70\" data-working-chip-unit> working</span>\n </span>\n <span\n // Round 201 / Loop: online chip — mirror of the working\n // chip treatment above. cyan hue 10→15 bg + 20→30 border\n // on hover, only when there's at least one online node\n // to highlight. Three sibling chips in the chip row now\n // all speak the same gesture vocabulary:\n // working chip · green 10→15 (R201)\n // online chip · cyan 10→15 (R201)\n // active-links · gray → cyan (R193)\n /* Round 232 / Loop: tabular-nums on online chip\n (sibling treatment to working chip — same row,\n same digit-jitter physics on count crossings). */\n className={`tabular-nums font-medium px-2.5 py-1 rounded-md border anet-topo-chip-focus transition-colors duration-200 ${\n onlineNodes.length > 0\n ? 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20 hover:bg-cyan-500/15 hover:border-cyan-500/30'\n : 'bg-cyan-500/10 text-cyan-300 border-cyan-500/20'\n }`}\n data-online-chip\n data-online-chip-aliases={onlineAliases.join(',')}\n data-pin-mirror={pinnedStatus === 'idle' ? 'true' : 'false'}\n data-online-chip-clickable={onlineNodes.length > 0 ? 'true' : 'false'}\n data-online-chip-empty={onlineNodes.length === 0 ? 'true' : 'false'}\n title={onlineTitle}\n role={onlineNodes.length > 0 ? 'link' : undefined}\n tabIndex={onlineNodes.length > 0 ? 0 : undefined}\n // R180: smooth-pin-mirror family — see working chip above.\n // R201: inline transition list also covers bg + border so\n // the new R201 hover tint eases (mirror of working chip).\n // R205: empty-tier recede — opacity 0.5 when onlineNodes\n // is empty (mirror of working chip above).\n style={{\n cursor: onlineNodes.length > 0 ? 'pointer' : undefined,\n opacity: onlineNodes.length === 0 ? 0.5 : 1,\n boxShadow: pinnedStatus === 'idle' ? 'inset 0 0 0 1px #67e8f9, inset 0 0 0 2px rgba(255,255,255,0.45)' : undefined,\n transition: 'box-shadow 150ms ease-out, background-color 200ms ease-out, border-color 200ms ease-out, opacity 200ms ease-out',\n }}\n onMouseEnter={() => {\n // If a working filter would isolate nothing, route to idle.\n const idleCount = onlineNodes.length - workingCount;\n if (workingCount > 0) setHoveredStatus('working');\n else if (idleCount > 0) setHoveredStatus('idle');\n }}\n onMouseLeave={() => setHoveredStatus(prev => prev === 'working' || prev === 'idle' ? null : prev)}\n // R140: click → /nodes. Mirrors R136 active-links→/messages\n // idiom. The chip's hover semantics (R79 highlight all\n // online) keep their meaning — hover for canvas preview,\n // click for the full list. Pinning idle here would\n // semantically misrepresent the chip (which means\n // working+idle), so we navigate instead of pin.\n onClick={() => {\n if (onlineNodes.length > 0) router.push('/nodes');\n }}\n onKeyDown={(e) => {\n if (onlineNodes.length === 0) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n router.push('/nodes');\n }\n }}\n >\n {/* R337 sibling — online chip unit demotion. */}\n {onlineNodes.length}<span className=\"opacity-70\" data-online-chip-unit> online</span>\n </span>\n </>\n );\n })()}\n {/* Round 31 / Loop: fleet-health pressure bar. The \"X working /\n Y online\" chips already carry the raw counts; the bar lets\n the eye get the working/idle/offline RATIO in one glance\n without mental math. Stacked 3-segment chip, ~64 px wide. */}\n {(() => {\n const w = workingCount;\n const i = onlineNodes.length - workingCount; // idle = online - working\n const o = offlineNodes.length;\n const total = w + i + o;\n if (total === 0) return null;\n // Round 60 / Loop: each segment toggles a sticky filter via\n // `pinnedStatus`. Click the working segment → all non-working\n // nodes dim; click again → release. Segments share width with\n // their proportion of `total`, so a 1-node working share is\n // ~3 px wide on a 64-px bar. We pad the click target with a\n // negative-margin overlay wrapper to give thin slices a\n // 14-px minimum hit area without disturbing the rendered\n // chip width. cursor:pointer + a one-line title hint at the\n // affordance.\n const seg = (n: number, color: string, key: 'working' | 'idle' | 'offline', label: string) => {\n if (n === 0) return null;\n const isPinned = pinnedStatus === key;\n // R102: list the aliases that match this segment's bucket\n // so the title answers WHICH n, not just HOW MANY. Closes\n // the last \"info-density gap\" in the chip-row surfaces\n // (R97 pills / R99 group labels / R101 vendor letters all\n // already enumerate). Truncates at 8 with \"+N more\".\n const matchAliases = key === 'working'\n ? onlineNodes.filter(s => s.status === 'working').map(s => s.alias)\n : key === 'idle'\n ? onlineNodes.filter(s => s.status !== 'working').map(s => s.alias)\n : offlineNodes.map(s => s.alias);\n const previewList = matchAliases.slice(0, 8).join(', ');\n const suffix = matchAliases.length > 8 ? ` + ${matchAliases.length - 8} more` : '';\n const titleAction = isPinned ? 'click to release filter' : 'click to highlight';\n return (\n <span\n key={key}\n data-pressure-seg={key}\n data-pressure-seg-aliases={matchAliases.join(',')}\n data-pressure-seg-hovered={hoveredStatus === key ? 'true' : 'false'}\n role=\"button\"\n tabIndex={0}\n aria-pressed={isPinned}\n className=\"anet-topo-chip-focus\"\n title={`${n} ${label}\\n${previewList}${suffix}\\n${titleAction}`}\n // Round 165 / Loop: smooth width transitions on the\n // pressure-bar segments. Pre-R165 the widths snapped\n // instantly when fleet composition shifted (a node\n // going idle → working would visibly jump the green\n // segment by a few px). 220ms ease-out makes the bar\n // visually breathe with state — segment shifts now\n // glide into place instead of cutting. Pure CSS\n // transition on width; respects prefers-reduced-\n // motion via globals.css blanket override that\n // neutralises transition-duration universally.\n // boxShadow gets its own transition so pin\n // state-changes also fade smoothly, not snap.\n //\n // Round 210 / Loop: segment deepens its OWN colour on\n // hover via filter: brightness(1.2). Closes the\n // chip-row \"hover deepens own identity\" family at the\n // pressure-bar scope — R83 already setHoveredStatus\n // to drive canvas dim, but the segment itself stayed\n // at flat `background: color`. R210 makes the cause\n // element (segment) light up with the same gesture\n // it fires on the canvas (effect element). Family\n // surfaces (6 with R210):\n // R193 active-links chip · gray → cyan\n // R195 recent-panel footer · gray → cyan\n // R201 working / online · own /10 → /15 (×2)\n // R202 vendor letter · per-vendor color-mix\n // R210 pressure-bar seg · brightness(1.2) ← NEW\n // brightness(1.2) lightens the segment's own hue by\n // 20% — keeps the tier identity (green stays green,\n // teal stays teal, gray stays gray) while signalling\n // \"this is hovered\". transition adds filter 150ms\n // ease-out alongside the existing width / box-shadow.\n style={{\n width: `${(n / total) * 100}%`,\n background: color,\n height: '100%',\n cursor: 'pointer',\n boxShadow: isPinned ? `inset 0 0 0 1px ${color}, inset 0 0 0 2px rgba(255,255,255,0.6)` : undefined,\n filter: hoveredStatus === key ? 'brightness(1.2)' : undefined,\n transition: 'width 220ms ease-out, box-shadow 150ms ease-out, filter 150ms ease-out',\n }}\n onClick={(e) => {\n e.stopPropagation();\n setPinnedStatus(prev => prev === key ? null : key);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setPinnedStatus(prev => prev === key ? null : key);\n }\n }}\n // R83: hover preview — segments now match the R55 legend,\n // R79 working/online chips, and R80 vendor letters in\n // offering a hover-transient highlight before the click\n // commits to a pin. Users get to feel what the filter\n // does before they lock it in. Same activeStatus =\n // hoveredStatus ?? pinnedStatus formula; releasing the\n // pointer falls back to the pin if one is set, or to\n // baseline. Thin (3-px) segments still hit-test fine\n // because the chip itself is a flex row — span doesn't\n // need any extra hit padding for hover.\n onMouseEnter={() => setHoveredStatus(key)}\n onMouseLeave={() => setHoveredStatus(prev => prev === key ? null : prev)}\n />\n );\n };\n // Round 47 / Loop: hidden on mobile — at <640px the chip row\n // wraps to multiple lines and crowds the topology header;\n // pressure ratio is best read with the working+online raw\n // counts (kept visible) anyway.\n return (\n <span\n className=\"hidden sm:inline-flex items-center gap-2 px-2.5 py-1 rounded-md bg-gray-500/10 text-gray-400 border border-gray-500/20 font-mono\"\n title={`${w} working · ${i} idle · ${o} offline`}\n data-fleet-pressure\n >\n <span className=\"text-[10px] tracking-wide\">pressure</span>\n <span className=\"inline-flex h-1.5 w-16 rounded-full overflow-hidden\" style={{ background: 'rgb(75 85 99 / 0.25)' }}>\n {seg(w, isLight ? '#059669' : '#22c55e', 'working', 'working')}\n {seg(i, isLight ? '#0d9488' : '#2dd4bf', 'idle', 'idle')}\n {seg(o, isLight ? '#94a3b8' : '#6b7280', 'offline', 'offline')}\n </span>\n </span>\n );\n })()}\n {/* Round 64 / Loop: active-filter pills. When pinnedStatus or\n pinnedGroup is set, show a small \"filter: <key> ×\" pill so\n the user can see the pin from the chip row even if they\n scrolled the canvas off-screen. × button clears the\n specific pin (Esc still clears all — both paths are\n valid). pinnedStatus and pinnedGroup pin independently so\n both pills may render simultaneously. */}\n {/* R67: pills enter via anet-fade-in so they appear softly\n instead of popping. The \"filter: \" prefix collapses below\n sm — at narrow viewports the chip row is precious real\n estate (R47 already hides pressure / vendor / freshness),\n so dropping the redundant label keeps the working/idle/\n alpha keys readable without overflow. */}\n {/* R71: each pill picks up a \"· N\" match count tail. Tells the\n user at a glance how many sessions the active filter\n matches without scanning the canvas. Counts come from the\n already-computed workingCount + onlineNodes + offlineNodes\n for status, and groupKeys for group. */}\n {/* R73: entire pill body is a click-to-clear target — matches\n the Notion / Linear tag UX (the whole chip releases). The\n × keeps its dedicated <button> + aria-label for screen\n readers; the outer span just adds an extra mouse-friendly\n hit area with a title hint. ×'s onClick stopPropagation\n so the redundant outer onClick doesn't double-fire (no\n functional difference since both clear, but cleaner\n event flow). */}\n {pinnedStatus && (() => {\n const matchCount = pinnedStatus === 'working' ? workingCount\n : pinnedStatus === 'idle' ? (onlineNodes.length - workingCount)\n : offlineNodes.length;\n // Round 97 / Loop: the pill says HOW MANY but not WHICH.\n // Hovering it now shows the matched alias list in the\n // tooltip — answers \"exactly who is this filtering to\"\n // without forcing the user to scan the dim mask on the\n // canvas. Truncates at 8 names so a 50-node working\n // bucket doesn't produce a 12-line tooltip; the count\n // chip already answers \"how many overall\".\n const matchAliases = pinnedStatus === 'working'\n ? onlineNodes.filter(s => s.status === 'working').map(s => s.alias)\n : pinnedStatus === 'idle'\n ? onlineNodes.filter(s => s.status !== 'working').map(s => s.alias)\n : offlineNodes.map(s => s.alias);\n const matchPreview = matchAliases.slice(0, 8).join(', ');\n const matchSuffix = matchAliases.length > 8 ? ` + ${matchAliases.length - 8} more` : '';\n return (\n <span\n data-active-filter=\"status\"\n data-filter-match-count={matchCount}\n data-filter-match-aliases={matchAliases.join(',')}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus\"\n title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}\n onClick={() => setPinnedStatus(null)}\n style={{\n background: pinnedStatus === 'working' ? (isLight ? '#05966914' : '#22c55e1f')\n : pinnedStatus === 'idle' ? (isLight ? '#0d948814' : '#2dd4bf1f')\n : (isLight ? '#94a3b814' : '#6b72801f'),\n color: pinnedStatus === 'working' ? (isLight ? '#047857' : '#86efac')\n : pinnedStatus === 'idle' ? (isLight ? '#0f766e' : '#5eead4')\n : (isLight ? '#475569' : '#9ca3af'),\n borderColor: 'currentColor',\n cursor: 'pointer',\n }}\n >\n <span><span className=\"hidden sm:inline opacity-70\" data-filter-prefix>filter: </span>{pinnedStatus}<span className=\"opacity-70 tabular-nums\" data-filter-pill-count> · {matchCount}</span></span>\n <button\n type=\"button\"\n aria-label={`Clear ${pinnedStatus} filter`}\n onClick={(e) => { e.stopPropagation(); setPinnedStatus(null); }}\n className=\"ml-0.5 leading-none hover:opacity-70\"\n style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}\n >×</button>\n </span>\n );\n })()}\n {pinnedGroup && (() => {\n const matchCount = Object.values(groupKeys).filter(k => k === pinnedGroup).length;\n // R97: list group members in the tooltip.\n const matchAliases = Object.entries(groupKeys)\n .filter(([, key]) => key === pinnedGroup)\n .map(([alias]) => alias);\n const matchPreview = matchAliases.slice(0, 8).join(', ');\n const matchSuffix = matchAliases.length > 8 ? ` + ${matchAliases.length - 8} more` : '';\n return (\n <span\n data-active-filter=\"group\"\n data-filter-match-count={matchCount}\n data-filter-match-aliases={matchAliases.join(',')}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus\"\n title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear filter'}\n onClick={() => setPinnedGroup(null)}\n style={{\n background: isLight ? '#67e8f914' : '#67e8f91f',\n color: pal.legendAccent,\n borderColor: 'currentColor',\n cursor: 'pointer',\n }}\n >\n <span><span className=\"hidden sm:inline opacity-70\" data-filter-prefix>filter: </span>{pinnedGroup}<span className=\"opacity-70 tabular-nums\" data-filter-pill-count> · {matchCount}</span></span>\n <button\n type=\"button\"\n aria-label={`Clear group filter ${pinnedGroup}`}\n onClick={(e) => { e.stopPropagation(); setPinnedGroup(null); }}\n className=\"ml-0.5 leading-none hover:opacity-70\"\n style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}\n >×</button>\n </span>\n );\n })()}\n {/* R89: vendor pin gets its own filter pill, matching the R64\n status + group pattern. R88 added the pin but only the\n letter itself carried the state; the chip-row had no\n \"filter: A · 2 ×\" affordance the other two pins have. The\n pill colour borrows the vendor's own swatch so each pin\n still reads in its native hue (A green, O cyan, 书 blue,\n ? slate). Same body-click-clears + × button pattern as\n R64. */}\n {pinnedVendor && (() => {\n const matchEntry = vendorDist.find(v => v.initial === pinnedVendor);\n const matchCount = matchEntry?.count ?? 0;\n const vendorColor = matchEntry?.color ?? pal.legendText;\n // R97: list vendor users in the tooltip. The vendorIdentity\n // resolver maps model strings to a vendor initial — match\n // any session whose initial equals the pinned letter (with\n // unknowns folded to '?').\n const matchAliases = [...onlineNodes, ...offlineNodes]\n .filter(s => {\n const v = vendorForModel(s.model);\n return (v.id === 'unknown' ? '?' : v.initial) === pinnedVendor;\n })\n .map(s => s.alias);\n const matchPreview = matchAliases.slice(0, 8).join(', ');\n const matchSuffix = matchAliases.length > 8 ? ` + ${matchAliases.length - 8} more` : '';\n return (\n <span\n data-active-filter=\"vendor\"\n data-filter-match-count={matchCount}\n data-filter-match-aliases={matchAliases.join(',')}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus\"\n title={matchCount > 0 ? `${matchPreview}${matchSuffix} — click to clear` : 'Click to clear vendor filter'}\n onClick={() => setPinnedVendor(null)}\n style={{\n background: `${vendorColor}1f`,\n color: vendorColor,\n borderColor: 'currentColor',\n cursor: 'pointer',\n }}\n >\n <span><span className=\"hidden sm:inline opacity-70\" data-filter-prefix>filter: </span>{pinnedVendor}<span className=\"opacity-70 tabular-nums\" data-filter-pill-count> · {matchCount}</span></span>\n <button\n type=\"button\"\n aria-label={`Clear vendor filter ${pinnedVendor}`}\n onClick={(e) => { e.stopPropagation(); setPinnedVendor(null); }}\n className=\"ml-0.5 leading-none hover:opacity-70\"\n style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}\n >×</button>\n </span>\n );\n })()}\n {/* R119: edge pin filter pill — completes the R64 / R89 pill\n pattern across all four pin dimensions. R116 added\n pinnedEdgeKey but the chip row never grew the matching\n pill, leaving the locked flow visible only on the canvas +\n recent-signal row tint. This pill shows \"filter:\n alpha→beta · 3\" so the locked flow appears in the same\n row as the other three pin types. Body-click + × button\n clear, same pattern as R64. */}\n {pinnedEdgeKey && (() => {\n const link = flowLinks.find(l => l.key === pinnedEdgeKey);\n if (!link) return null;\n // R150 / Loop: extend the hot-lane amber convention from the\n // canvas badge (R126) / recent-row count (R127) / panel\n // header (R129) to the R119 edge filter pill — when the\n // pinned edge has count ≥ 10 the count tspan flips to\n // amber + 700 weight, and the tooltip grows a \"(hot lane\n // · ≥ 10)\" marker. Closes the 4th hot-lane surface,\n // completing R150's milestone: every place that surfaces\n // an edge count now uses the same amber-when-hot vocab.\n const isHot = link.count >= 10;\n const hotStroke = isLight ? '#d97706' : '#fbbf24';\n return (\n <span\n data-active-filter=\"edge\"\n data-filter-match-count={link.count}\n data-filter-match-aliases={`${link.from},${link.to}`}\n data-active-filter-edge-hot={isHot ? 'true' : 'false'}\n className=\"inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono font-medium text-xs border anet-fade-in anet-topo-chip-focus\"\n title={`${link.from} → ${link.to} (${link.count} msg${link.count === 1 ? '' : 's'}${isHot ? ', hot lane · ≥ 10' : ''}) — click to clear`}\n onClick={() => setPinnedEdgeKey(null)}\n style={{\n background: isLight ? `${pal.flowEdge}14` : `${pal.flowEdge}1f`,\n color: pal.flowEdge,\n borderColor: 'currentColor',\n cursor: 'pointer',\n }}\n >\n <span>\n <span className=\"hidden sm:inline opacity-70\" data-filter-prefix>filter: </span>\n {link.from}→{link.to}\n {/* Round 323 / Loop: edge filter pill count digit picks\n up tabular-nums (Tailwind class on both cold +\n hot branches). Sibling treatment to the status /\n group / vendor pin pills (R323 replace_all upstream\n in this same round added `tabular-nums` to those\n three pills' count spans). Pre-R323 a matchCount /\n link.count crossing 9→10 widened the digit and\n shifted the trailing × button right ~3px in font-\n mono (mono digits still have natural-vs-tabular\n variance). Locks the slot so the × button stays\n planted as the count grows. 9th surface in the\n info-density tabular-nums sweep after R322 panel\n hot count. */}\n {isHot ? (\n <span\n className=\"opacity-90 tabular-nums\"\n style={{ color: hotStroke, fontWeight: 700 }}\n data-active-filter-edge-count-hot\n >\n {' · '}{link.count}\n </span>\n ) : (\n <span className=\"opacity-70 tabular-nums\" data-active-filter-edge-count>\n {' · '}{link.count}\n </span>\n )}\n </span>\n <button\n type=\"button\"\n aria-label={`Clear edge filter ${link.from} → ${link.to}`}\n onClick={(e) => { e.stopPropagation(); setPinnedEdgeKey(null); }}\n className=\"ml-0.5 leading-none hover:opacity-70\"\n style={{ background: 'transparent', color: 'inherit', cursor: 'pointer', padding: 0 }}\n >×</button>\n </span>\n );\n })()}\n {/* Round 124 / Loop: pin-intersection summary chip. The four\n filter pills (R64 status, R63 group, R89 vendor, R119\n edge) each report their own dim's match count in\n isolation — that answers \"what does THIS filter catch\"\n but not \"what survives ALL pins\". Since the node-opacity\n chain AND-composes the four pin dimensions, two active\n pins routinely produce an intersection smaller than\n either individual count. With three or four pins active\n the gap widens further. This chip appears ONLY when\n ≥ 2 pin dims are active (a single pin's pill already\n tells the whole story) and shows the count of nodes\n that satisfy every active pin simultaneously. Color is\n deliberately neutral (gray) since the chip represents\n the AND of mixed-color filters — borrowing any one of\n the pill hues would mis-signal which dim dominates.\n Tooltip lists the surviving aliases with the same\n 8-truncate + \"+N more\" pattern as the individual pill\n tooltips (R97). No click handler — it's a pure readout;\n Esc / per-pill × are still the cancel paths. */}\n {(() => {\n const pinDimCount =\n (pinnedStatus ? 1 : 0) +\n (pinnedGroup ? 1 : 0) +\n (pinnedVendor ? 1 : 0) +\n (pinnedEdgeKey ? 1 : 0);\n if (pinDimCount < 2) return null;\n const edgeLink = pinnedEdgeKey\n ? flowLinks.find(l => l.key === pinnedEdgeKey)\n : null;\n const edgeEndpoints: Set<string> | null = edgeLink\n ? new Set([edgeLink.from, edgeLink.to])\n : null;\n const allSessions = [...onlineNodes, ...offlineNodes];\n const survivors = allSessions.filter(s => {\n const isOnline = s.status !== 'offline';\n if (pinnedStatus === 'working' && s.status !== 'working') return false;\n if (pinnedStatus === 'idle' && !(isOnline && s.status !== 'working')) return false;\n if (pinnedStatus === 'offline' && isOnline) return false;\n if (pinnedGroup) {\n const gk = groupKeys[s.alias] ?? s.alias;\n if (gk !== pinnedGroup) return false;\n }\n if (pinnedVendor) {\n const v = vendorForModel(s.model);\n const initial = v.id === 'unknown' ? '?' : v.initial;\n if (initial !== pinnedVendor) return false;\n }\n if (edgeEndpoints && !edgeEndpoints.has(s.alias)) return false;\n return true;\n });\n const matchAliases = survivors.map(s => s.alias);\n const matchPreview = matchAliases.slice(0, 8).join(', ');\n const matchSuffix = matchAliases.length > 8 ? ` + ${matchAliases.length - 8} more` : '';\n const isEmpty = matchAliases.length === 0;\n const tooltip = !isEmpty\n ? `${matchPreview}${matchSuffix} — nodes passing all ${pinDimCount} pinned filters`\n : `No nodes pass all ${pinDimCount} pinned filters — release one to widen (Esc clears all)`;\n // R125: when the intersection drops to zero, the chip flips\n // from neutral gray to a warning amber. Zero-overlap is the\n // exact case users get confused — canvas dims to 0.28\n // everywhere, no positive signal explains why. The \"· 0\"\n // tail in neutral gray reads as just another number,\n // indistinguishable from \"· 12\". Amber + a ⚠ glyph lifts\n // it to \"your filters cancel out\" at a glance. Color\n // choice: amber (#d97706 light, #fbbf24 dark) — same hue\n // family as warning chips elsewhere in the dashboard,\n // distinct from any pill color (status green/teal/slate,\n // group cyan, vendor varies, edge cyan, neutral gray for\n // non-empty intersection) so the empty state stands out.\n const emptyColor = isLight ? '#d97706' : '#fbbf24';\n return (\n <span\n data-pin-intersection\n data-pin-dim-count={pinDimCount}\n data-pin-intersection-count={matchAliases.length}\n data-pin-intersection-empty={isEmpty ? 'true' : 'false'}\n data-pin-intersection-aliases={matchAliases.join(',')}\n /* Round 235 / Loop: pin-intersection chip joins the\n info-density tabular-nums sweep. The chip has TWO\n digits visible at once — '{pinDimCount} pins ·\n {matchAliases.length}' — and the matchAliases count\n in particular rolls frequently as filters tighten /\n widen against the live fleet. font-mono already\n makes the digits uniform-ish, but tabular-nums\n further locks digit width within the mono cell so\n the gap between 'pins' and '·' stays stable, and\n the chip's overall width doesn't bump when either\n number changes. 9th surface in the sweep — the\n third and last HTML chip surface, completing\n coverage across chip-row + vendor-row + pin-\n intersection. */\n className=\"hidden sm:inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md font-mono tabular-nums text-xs border anet-fade-in anet-topo-chip-focus\"\n title={tooltip}\n /* Round 236 / Loop: smooth the empty/non-empty colour\n crossing. Pre-R236 the chip snap-flipped between\n slate-on-slate (non-empty filter intersection) and\n amber-on-amber (empty intersection — the 'your\n pinned filters cancel out' warning). bg, color,\n and borderColor (which inherits currentColor) all\n changed in one frame. R236 adds a 200ms ease-out\n transition on all three so when a filter tightens\n matches across the 0-boundary the chip eases\n through the colour shift instead of snapping. Same\n 200ms cadence the other chip-row members use\n (R201 working/online tint, R193 active-links\n tint, R202 vendor letter color-mix). One more\n surface where colour state-changes ease rather\n than snap — consistent with the topology's\n broader transitions vocabulary. */\n style={{\n background: isEmpty\n ? (isLight ? '#d97706' + '14' : '#fbbf24' + '1f')\n : (isLight ? '#94a3b814' : '#94a3b81f'),\n color: isEmpty\n ? emptyColor\n : (isLight ? '#475569' : '#9ca3af'),\n borderColor: 'currentColor',\n transition: 'background-color 200ms ease-out, color 200ms ease-out, border-color 200ms ease-out',\n }}\n >\n <span>\n <span className=\"hidden sm:inline opacity-70\" data-pin-intersection-prefix>match: </span>\n {/* Round 324 / Loop: pin-intersection chip carries TWO\n numeric counts in one breath — pinDimCount (\"how\n many filter pins are active\") and matchAliases.\n length (\"how many aliases land in the intersection\n after pins compose\"). Both jitter on digit-width\n crossings (1→10 etc) without tabular-nums even\n under font-mono. Pre-R324 a fleet busying up so\n one dimension flips from 0→non-zero (chip mounts\n via R237 always-mount opacity gate) AND the match\n count digit ticks 9→10 simultaneously visibly\n jolted the trailing `× pins` / ` × ` segments.\n Two dedicated tabular-nums spans (one per count)\n lock both digit slots so the chip's text geometry\n stays planted through both crossings. 10th\n surface in the info-density tabular-nums sweep\n after R323 filter pin pill counts (R64/R89/R119/\n R150 pin-pill family parity now complete with\n this composed-pin sibling). data-pin-intersection-\n count-* attrs expose both spans for tests. */}\n {/* Round 341 / Loop: middle \" pins\" unit word\n previously sat as a bare text node between the\n two count spans, while the matches-count span\n already carried opacity-0.7 (R335 + R324 era).\n The pinDimCount span is prominent and the\n matches count is recessive — but the literal\n \" pins\" was at FULL opacity, breaking the\n chip-internal hierarchy unified across R333/\n R335/R336/R337/R338/R340. R341 wraps \" pins\"\n in an opacity-0.7 span so the chip reads:\n pinDimCount (prominent value)\n \" pins\" (recessive unit)\n \" · {N}\" (recessive count)\n Three-tier hierarchy on a single chip; 7th\n surface in the chip-internal-hierarchy arc. */}\n <span className=\"tabular-nums\" data-pin-intersection-count-dims>{pinDimCount}</span><span className=\"opacity-70\" data-pin-intersection-unit> pins</span><span className=\"opacity-70 tabular-nums\" data-pin-intersection-count-matches> · {matchAliases.length}</span>\n {/* Round 237 / Loop: ⚠ warning glyph picks up the\n always-mount-opacity-gate idiom. Pre-R237 the\n glyph was conditionally rendered on isEmpty,\n snap-mounting when filter intersection\n narrowed to 0 AND introducing a layout shift\n (ml-1 margin appears alongside the glyph,\n widening the chip by ~16px). The R236 color\n easing made the colour crossing smooth but\n the glyph still pop-jumped, breaking the\n polish that R236 just installed at this\n same chip.\n\n Always-mount the glyph with opacity gated by\n isEmpty + the same 200ms ease-out that R236\n uses on the chip's colour transition. Now the\n whole isEmpty crossing — bg, color, border,\n AND glyph visibility — eases as one\n coordinated 200ms event. ml-1 margin is\n reserved permanently, so the chip width\n stays stable through the crossing (no\n layout-shift jank against neighbouring\n chips). data-pin-intersection-warning attr\n surfaces the visibility state for test\n introspection. 11th surface in the always-\n mount-opacity-gate family (R181 / R182 /\n R183 / R213 ×2 / R214 / R215 / R221 / R222 /\n R223 / R237). */}\n <span\n className=\"ml-1\"\n aria-hidden\n data-pin-intersection-warning={isEmpty ? 'true' : 'false'}\n style={{ opacity: isEmpty ? 1 : 0, transition: 'opacity 200ms ease-out' }}\n >⚠</span>\n </span>\n </span>\n );\n })()}\n {/* Round 281 / Loop: vendor letters chip threshold tightens\n from >1 to >2 per 减法 cut #7. Pre-R281 the chip showed\n whenever ≥2 vendor types existed in the fleet — for a\n typical demo (claude + 1 other = 2 types), the chip\n rendered \"A:N C:M\" adding ~50-80px to the chip-row width.\n Tightening to >2 keeps the chip useful for fleets with\n ACTUAL vendor diversity (3+ types) where the\n composition matters at a glance, but hides it for the\n common 1-2 vendor case where the info is low-signal.\n Continues the R275-R280 simplification arc. */}\n {vendorDist.length > 2 && (\n <span\n className=\"hidden sm:inline-flex items-center gap-2 px-2.5 py-1 rounded-md bg-gray-500/10 text-gray-400 border border-gray-500/20 font-mono\"\n title=\"Hover to highlight; click to pin\"\n >\n {vendorDist.map(v => {\n const isPinned = pinnedVendor === v.initial;\n // R88: click toggles a sticky filter the same way R60\n // pressure-bar segments toggle pinnedStatus. Visual\n // mirror = inset boxShadow using the vendor's own\n // colour, so each pinned letter sings in its own hue\n // (Anthropic green / OpenAI cyan / 书 blue / ?).\n // R101: tooltip lists the aliases that use this vendor —\n // completes the info-density triple started by R97 pills,\n // R98 node titles, R99 group-label titles. Anywhere the\n // UI shows \"A:3\" should hover-explain which 3.\n const aliases = [...onlineNodes, ...offlineNodes]\n .filter(s => {\n const vid = vendorForModel(s.model);\n return (vid.id === 'unknown' ? '?' : vid.initial) === v.initial;\n })\n .map(s => s.alias);\n const preview = aliases.slice(0, 8).join(', ');\n const suffix = aliases.length > 8 ? ` + ${aliases.length - 8} more` : '';\n const tooltip = isPinned\n ? `${preview}${suffix} — click again or Esc to clear`\n : `${preview}${suffix} — click to pin`;\n return (\n <span\n key={v.initial}\n role=\"button\"\n tabIndex={0}\n aria-pressed={isPinned}\n /* Round 234 / Loop: vendor letter chip picks up\n tabular-nums to lock the ':N' suffix's digit\n width. Each vendor chip renders 'A:3', 'C:2',\n etc. inline at gap-0.5 — when one vendor's\n count rolls 9→10 the chip widens by the digit\n glyph delta and pushes downstream chips right,\n making the row visibly jitter. 8th surface\n in the info-density tabular-nums sweep,\n completing the HTML chip-side coverage after\n R232 working/online/active-links chips. The\n digit lives in the inner <span> at line 2194,\n but font-variant-numeric inherits, so applying\n it at the outer chip span reaches every\n descendant glyph for free. */\n /* Round 314 / Loop: vendor letter chip joins the\n R312-R313 'HTML-context data chip = font-medium'\n family. R313 weighted the 3 main chips\n (working/online/active-links); R314 closes the\n chip-row weight sweep by extending to the\n vendor letter chips ('A:N', 'O:N', '书:N',\n '?:N'). They display vendor-distribution\n data; same tier as the sibling data chips. */\n className=\"tabular-nums font-medium inline-flex items-baseline gap-0.5 px-1 rounded anet-topo-chip-focus\"\n data-vendor-letter={v.initial}\n data-vendor-letter-count={v.count}\n data-vendor-pinned={isPinned ? 'true' : 'false'}\n data-vendor-hovered={hoveredVendor === v.initial ? 'true' : 'false'}\n data-vendor-aliases={aliases.join(',')}\n title={tooltip}\n // R180: smooth-pin-mirror family — see working chip above.\n // Round 202 / Loop: vendor letter chip joins the \"hover\n // deepens own identity hue\" family R193/R195/R201 built\n // up across the rest of the chip row. Pre-R202 hovering\n // a vendor letter (A/C/G/K/书/?) fired R88 canvas dim\n // via setHoveredVendor, but the chip itself stayed at\n // bg=transparent — cause silent, effect loud. R202\n // tints the chip with its OWN vendor colour at 12%\n // alpha via color-mix() so each vendor's chip lights\n // up in its own hue (Anthropic green / OpenAI cyan /\n // 书 blue / ?). No layout shift: only background-color\n // changes, no border/padding swap. transition list\n // extends the existing R180 box-shadow 150ms with\n // background-color 200ms ease-out (same splice idiom\n // R201 used on the working/online chips). color-mix()\n // is supported Chrome ≥ 111 / Safari ≥ 16.2 / FF ≥ 113;\n // for older browsers the chip falls back to its idle\n // transparent bg (graceful degradation — the canvas-\n // dim effect still fires regardless).\n style={{\n cursor: 'pointer',\n backgroundColor: (hoveredVendor === v.initial && !isPinned)\n ? `color-mix(in srgb, ${v.color} 12%, transparent)`\n : 'transparent',\n boxShadow: isPinned\n ? `inset 0 0 0 1px ${v.color}, inset 0 0 0 2px rgba(255,255,255,0.45)`\n : undefined,\n transition: 'box-shadow 150ms ease-out, background-color 200ms ease-out',\n }}\n onMouseEnter={() => setHoveredVendor(v.initial)}\n onMouseLeave={() => setHoveredVendor(prev => prev === v.initial ? null : prev)}\n onClick={() => setPinnedVendor(prev => prev === v.initial ? null : v.initial)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setPinnedVendor(prev => prev === v.initial ? null : v.initial);\n }\n }}\n >\n <span style={{ color: v.color }}>{v.initial}</span>\n {/* Round 333 / Loop: vendor count suffix `:{N}` joins\n the R317 subordinate-text-lift family (gray-500 →\n gray-400) plus picks up tabular-nums for digit\n width-lock. Pre-R333 a vendor whose count\n crossed 9→10 widened the suffix and (since the\n parent chip has `px-2.5` padding but no fixed\n width) shifted the chip-row's downstream chips\n a couple px right. Tabular-nums locks the slot;\n gray-400 lifts the digit into the band where eye\n reads it as \"deliberate subordinate metadata\"\n rather than near-invisible chrome. data-vendor-\n letter-count exposes the span for tests. */}\n <span\n className=\"text-gray-400 tabular-nums\"\n data-vendor-letter-count-suffix\n >:{v.count}</span>\n </span>\n );\n })}\n </span>\n )}\n {/* Round 42 / Loop: extend active-links chip with the timestamp\n of the most-recent flow event. Tells the operator at a glance\n whether the topology is currently humming (last 30s) or has\n been quiet for a while — the visual flow particles and\n edge brightness only show that there IS traffic, not when\n the last one was. Reuses Round 38's relativeAgo. */}\n {(() => {\n const recent = flowLinks.reduce<number | null>((acc, l) => {\n if (!l.last_at) return acc;\n const t = parseHubTime(l.last_at);\n if (t === null) return acc;\n return acc === null || t > acc ? t : acc;\n }, null);\n const rel = recent !== null ? relativeAgo(new Date(recent).toISOString()) : null;\n // R114: tooltip lists the actual flows. Closes the\n // info-density sweep on the last chip-row hover surface\n // (R97-R113 covered everything else). Format:\n // \"alpha→beta (3), gamma→delta (1) — hover brightens all\"\n // Truncates at 6 flows with \"+N more\" so a busy fleet\n // doesn't paint a tall tooltip; the recent-signal panel\n // already shows the top 3 in detail.\n const flowList = flowLinks\n .slice(0, 6)\n .map(l => `${l.from}→${l.to} (${l.count})`)\n .join(', ');\n const flowSuffix = flowLinks.length > 6 ? ` + ${flowLinks.length - 6} more` : '';\n // R136: the chip already had cursor:pointer when flowLinks\n // > 0 (line 1877) — but no onClick was wired. The cursor\n // lied; users got the affordance signal with no follow-\n // through. Wire it to /messages, mirroring R133's footer-\n // nav idiom. Hover (R77) keeps its semantic \"preview all\n // flows on canvas\"; click is the action \"open the full\n // list\". Two distinct gestures, both meaningful. The\n // tooltip grows a \"click to open\" tail when interactive.\n // Drop the chip out of click territory entirely when\n // flowLinks is empty — no flows = no list to open.\n const isInteractive = flowLinks.length > 0;\n const tooltip = !isInteractive\n ? undefined\n : `${flowList}${flowSuffix} — hover brightens all · click to open /messages`;\n return (\n // Round 193 / Loop: the chip itself adopts a subtle cyan\n // tint on its own hover, mirroring the cyan flowEdge\n // highlight it fires on the canvas. Pre-R193 the gesture\n // was visually asymmetric:\n // hover this chip → canvas edges brighten (cyan)\n // chip itself → stays gray (silent)\n // The *cause element* (chip) gave no response while the\n // *effect element* (canvas edges) painted loud. Same\n // pin-mirror logic R165 / R180 use on the four other\n // chip-row surfaces — the chip and the canvas edge it\n // pins should speak the same color vocabulary. Tailwind\n // :hover variant cyan-500/10 bg + cyan-500/30 border +\n // cyan-200 text matches the same palette R178/R163 use\n // for the active chrome buttons. transition-colors\n // duration-200 blends the swap smoothly. Hover variant\n // only attaches when isInteractive — a chip showing\n // \"0 active links\" has no list to open and should\n // stay gray. data-active-links-clickable already\n // exposes that gate to tests.\n <span\n // Round 206 / Loop: extend the R204/R205 empty-recede\n // family to the active-links chip. Pre-R206 \"0 active\n // links\" rendered at the same bg-gray-500/10 + full\n // opacity as \"12 active links\" — same eye-no-signal\n // problem the legend count (R204) and working/online\n // chips (R205) just solved at their own grain levels.\n // Inline opacity 0.5 when !isInteractive (flowLinks=0)\n // joins R136's already-removed cursor + R114's tooltip-\n // text gate to give the empty state visual + interactive\n // + affordance signals in lockstep.\n // Tailwind transition-colors duration-200 on className\n // would be overridden by the inline transition list, so\n // we replicate color/bg/border transitions inline\n // alongside the new opacity 200ms — same splice idiom\n // R201 used on the working/online chips.\n /* Round 232 / Loop: tabular-nums on active-links chip\n (third chip in the row — matches working + online\n chip treatment so all three digits in the chip row\n stay width-stable across counter crossings). */\n className={`tabular-nums font-medium hidden sm:inline px-2.5 py-1 rounded-md border anet-topo-chip-focus ${\n isInteractive\n ? 'bg-gray-500/10 text-gray-400 border-gray-500/20 hover:bg-cyan-500/10 hover:text-cyan-200 hover:border-cyan-500/30'\n : 'bg-gray-500/10 text-gray-400 border-gray-500/20'\n }`}\n data-active-links-chip\n data-active-links-flow-count={flowLinks.length}\n data-active-links-clickable={isInteractive ? 'true' : 'false'}\n data-active-links-empty={isInteractive ? 'false' : 'true'}\n title={tooltip}\n role={isInteractive ? 'link' : undefined}\n tabIndex={isInteractive ? 0 : undefined}\n style={{\n cursor: isInteractive ? 'pointer' : undefined,\n opacity: isInteractive ? 1 : 0.5,\n transition: 'color 200ms ease-out, background-color 200ms ease-out, border-color 200ms ease-out, opacity 200ms ease-out',\n }}\n onMouseEnter={() => { if (isInteractive) setHoveredActiveLinks(true); }}\n onMouseLeave={() => setHoveredActiveLinks(false)}\n onClick={() => { if (isInteractive) router.push('/messages'); }}\n onKeyDown={(e) => {\n if (!isInteractive) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n router.push('/messages');\n }\n }}\n >\n {/* R338 — active-links chip digit/unit split, completes\n the 5th chip surface in the R333/R335/R336/R337\n chip-internal-hierarchy arc. data-active-links-\n chip-unit exposes the unit span for tests. */}\n {flowLinks.length}<span className=\"opacity-70\" data-active-links-chip-unit> active link{flowLinks.length === 1 ? '' : 's'}</span>\n {rel ? (() => {\n // Round 161 / Loop: extend R160's recency-pip\n // vocabulary up one scope — from per-flow row to\n // fleet-aggregate chip. The chip already shows\n // recency in text (\"last 5s ago\"); the \" · \"\n // separator bullet was dead gray. Color the\n // bullet by freshness using the same alpha\n // ramp R160 uses on recent-signal rows:\n // ageSec ≤ 30 → 1.0 (fully fresh)\n // 30-300s → smooth decay 1.0 → 0.25\n // > 300s → 0.25 (stale floor)\n // Cyan bullet pulse when fresh + gray text tail\n // = \"is the network firing right now\" readable\n // at a glance, without parsing the timestamp\n // numerals. Same vocabulary the canvas uses\n // (R10 edge fade) and the recent-signal panel\n // uses (R160 row pip) — three nested scopes\n // now share one freshness ladder.\n const ageSec = recent !== null\n ? Math.max(0, (Date.now() - recent) / 1000)\n : 999;\n const alpha = ageSec <= 30\n ? 1\n : ageSec <= 300\n ? 1 - ((ageSec - 30) / 270) * 0.75\n : 0.25;\n // Cyan dark / teal light to match palette legendAccent.\n const dotColor = isLight\n ? `rgba(13, 148, 136, ${alpha.toFixed(2)})`\n : `rgba(34, 211, 238, ${alpha.toFixed(2)})`;\n // Round 342 / Loop: active-links chip freshness suffix\n // wrapper text-gray-500 → text-gray-400 (R317\n // subordinate-text-lift family applied to chrome\n // inactive Layout toggle + R333 vendor count suffix).\n // The \"last 5s ago\" suffix is chip-subordinate\n // metadata; gray-500 sat near-invisible against the\n // chip's outer color, gray-400 lifts it into the band\n // where the eye reads it as deliberate freshness\n // annotation. The freshness DOT keeps its own inline\n // color: dotColor — the lift only affects the trailing\n // literal \"last {rel}\" text.\n return (\n <span className=\"text-gray-400\">\n <span\n data-active-links-freshness-dot\n data-active-links-freshness-alpha={alpha.toFixed(2)}\n style={{\n color: dotColor,\n fontWeight: alpha > 0.7 ? 700 : 400,\n transition: 'color 200ms ease-out',\n }}\n >{' · '}</span>\n last {rel}\n </span>\n );\n })() : null}\n </span>\n );\n })()}\n <FreshnessChip sessions={sessions} />\n </div>\n </div>\n\n <div\n ref={containerRef}\n /* Round 330 / Loop (milestone): canvas wrapper rounded-lg\n → rounded-xl (8px → 12px corner radius). The biggest\n single surface on the dashboard by pixel area now reads\n as modern-SaaS-contemporary rather than 2020-conservative\n — same 4px bump R197 applied to the legend swatch and\n R295 applied to the title-block crescent. R330 ports\n the gesture to the OUTER envelope.\n Inner content (SVG viewBox 1000×680) sits behind\n `overflow-hidden`, so corner-radius change only affects\n the wrapper's own paint area and the shadow contour;\n the topo-overlap-test reads SVG-internal geometry and is\n unaffected. R254 background-color / R254 border-color /\n R263 box-shadow transitions all carry through unchanged.\n Marks R330 milestone of 5 rounds (R326-R330) of layout-\n geometry polish (gap-tier + crescent fade + trailer\n compensator + corner radius). */\n className={`relative overflow-hidden rounded-xl border shadow-2xl ${isLight ? 'shadow-zinc-900/5' : 'shadow-cyan-950/30'} ${isFullscreen ? 'flex items-center justify-center' : ''}`}\n data-topo-wrapper\n /* Round 254 / Loop: top-level TopoGraph wrapper gains theme-\n toggle transition. This is the BIGGEST theme-driven surface\n on the dashboard by pixel area — pal.containerBg fills the\n entire visible canvas area (cyber #080814 ↔ light #ffffff),\n and pal.containerBorder rims it. Pre-R254 every inner\n element eased through theme but the outer wrapper hard-cut,\n visually anchoring the snap. R253 declared\n \"no visible snap remains\" prematurely — this wrapper was\n the largest holdout. 200ms ease-out matches the panel\n treatment (R247) so wrapper + panels ease as one unit.\n\n Round 263 / Loop: close R254's holdover gap — the wrapper's\n shadow-2xl + theme-conditional `shadow-{color}/{opacity}`\n Tailwind class (cyber `shadow-cyan-950/30` ↔ light\n `shadow-zinc-900/5`) ALSO changes on theme toggle, but the\n inline transition list only covered background-color +\n border-color. Result: every inner element eased through\n theme, the wrapper bg/border eased, but the wrapper's\n DROP-SHADOW snapped — a subtle but real holdover from\n R254's \"TRULY complete\" claim. Adding `box-shadow 200ms\n ease-out` to the transition list catches the className-\n driven box-shadow swap (CSS transition on box-shadow eases\n the shadow property even when its color comes from a\n Tailwind class change, because the property itself is\n transition-eligible regardless of source). */\n style={{\n background: pal.containerBg,\n borderColor: pal.containerBorder,\n transition: 'background-color 200ms ease-out, border-color 200ms ease-out, box-shadow 200ms ease-out',\n }}\n >\n {/* Round 265 / Loop: top-rail (1px-tall colored line at the top\n of the canvas wrapper) picks up theme-toggle transition.\n Pre-R265 the className `bg-gradient-to-r ${pal.topRail\n Gradient}` was theme-conditional — cyber `via-cyan-400/70`\n ↔ light `via-emerald-500/40` — but no inline transition,\n so the rail SNAPPED on theme flip while the wrapper bg\n (R254) + border (R254) + shadow (R263) all eased. The top-\n rail is the THIN BRIGHT LINE that visually anchors the\n canvas top edge — its hard color flip was a small but real\n theme-snap that broke the otherwise-eased canvas envelope.\n transition: background-image catches the className-driven\n gradient swap; Chrome ≥ 89 / Safari ≥ 14.1 / FF ≥ 96\n interpolate linear-gradients with matching stop structures\n (both gradients are `from-transparent via-X to-transparent`\n → same 3-stop layout). data-topo-top-rail makes the probe\n deterministic. */}\n <div\n className={`absolute inset-x-0 top-0 h-px bg-gradient-to-r ${pal.topRailGradient}`}\n data-topo-top-rail\n style={{ transition: 'background-image 200ms ease-out' }}\n />\n\n {/* Round 158 / Loop: give the canvas SVG itself an accessible\n name + role description. R151-R157 added a11y to every\n interactive surface inside the canvas (nodes, group labels,\n badges, rows, minimap, chrome buttons) — but the canvas\n container itself was a nameless 1000×680 box. A screen\n reader that hit the SVG before tab-diving into its\n children heard nothing identifying it as \"the topology\".\n aria-roledescription gives it a meaningful announcement;\n aria-label provides a live snapshot of the network\n (online / working / active links / offline) plus the two\n canvas-scope gestures (Tab + double-click). Default SVG\n role is graphics-document so children remain navigable —\n we deliberately don't override role with \"img\" which\n would flatten the SVG to a single opaque image. */}\n <svg\n ref={svgRef}\n viewBox=\"0 0 1000 680\"\n className=\"w-full h-auto block\"\n preserveAspectRatio=\"xMidYMid meet\"\n aria-roledescription=\"agent network topology\"\n aria-label={(() => {\n const online = onlineNodes.length;\n const working = workingCount;\n const offline = offlineNodes.length;\n const flows = flowLinks.length;\n const parts: string[] = [];\n parts.push(`${online} agent${online === 1 ? '' : 's'} online`);\n if (working > 0) parts.push(`${working} working`);\n if (offline > 0) parts.push(`${offline} offline`);\n parts.push(`${flows} active link${flows === 1 ? '' : 's'}`);\n return `Agent network topology — ${parts.join(' · ')}. Tab to navigate nodes, double-click canvas to reset view.`;\n })()}\n data-topo-canvas-aria\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerLeave={onPointerUp}\n // Round 41 / Loop: the reset-button title (R22) and the Help\n // overlay (R25) both advertise \"double-click the canvas to\n // reset\", but the handler was never actually wired. The text\n // was lying. Wire it here, guarded so dbl-clicking a node\n // (which would also trigger the SVG-level handler via event\n // bubbling) doesn't unexpectedly reset the view on the user.\n onDoubleClick={(e) => {\n const t = e.target as Element | null;\n if (t?.closest('g[data-node]')) return;\n resetView();\n }}\n style={{ cursor: isPanning ? 'grabbing' : 'grab', touchAction: 'none' }}\n >\n <defs>\n <linearGradient id=\"topo-panel\" x1=\"0\" x2=\"1\" y1=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor={pal.panelStops[0]} />\n <stop offset=\"48%\" stopColor={pal.panelStops[1]} />\n <stop offset=\"100%\" stopColor={pal.panelStops[2]} />\n </linearGradient>\n <radialGradient id=\"topo-radar\" cx=\"50%\" cy=\"50%\" r=\"55%\">\n <stop offset=\"0%\" stopColor={pal.radarStops[0].color} stopOpacity={pal.radarStops[0].opacity} />\n <stop offset=\"45%\" stopColor={pal.radarStops[1].color} stopOpacity={pal.radarStops[1].opacity} />\n <stop offset=\"100%\" stopColor={pal.radarStops[2].color} stopOpacity={pal.radarStops[2].opacity} />\n </radialGradient>\n {!isLight && (\n <filter id=\"topo-glow\">\n <feGaussianBlur stdDeviation=\"4\" result=\"blur\" />\n <feMerge>\n <feMergeNode in=\"blur\" />\n <feMergeNode in=\"SourceGraphic\" />\n </feMerge>\n </filter>\n )}\n {/* R142 / Loop: drop-shadow filter for group-box hover-lift.\n Mirrors R135's panel hover-elevation idiom but applied\n at the per-group canvas level. R68 already gives a\n hovered/pinned group box a solid accent stroke; R142\n adds a soft outward shadow on top so the box visually\n \"rises off the canvas\" when selected — same visual\n vocabulary as the panels + the Overview KPI cards\n (R18). Theme-aware flood: darker shadow on cyber so\n it reads above the dark canvas, lighter for light\n theme. Bounding box of the group box is unchanged —\n filters only affect paint area, not bbox geometry, so\n the overlap-test invariant is preserved. */}\n <filter id=\"topo-groupbox-lift\" x=\"-10%\" y=\"-10%\" width=\"120%\" height=\"120%\">\n <feDropShadow\n dx=\"0\" dy=\"3\" stdDeviation=\"4\"\n floodColor={isLight ? '#0f172a' : '#000000'}\n floodOpacity={isLight ? 0.18 : 0.55}\n />\n </filter>\n {/* Round 16 / Loop: 3-tier flow-link arrow markers.\n The single marker had `markerUnits` defaulting to\n `strokeWidth`, so heavy edges (stroke=7) rendered 35-user-\n unit arrowheads — visually dominant, the head outweighed\n the line. Switching to `userSpaceOnUse` decouples arrow\n size from stroke; binning by count gives a clearer\n hierarchy than continuous linear scaling:\n s (count 1-2) → 12 user units\n m (count 3-4) → 16 user units (alias for `topo-arrow`)\n l (count 5+) → 22 user units\n `topo-arrow` stays bound to the medium tier so the legend\n swatch (line ~1500) renders without change. */}\n {[\n { id: 'topo-arrow-s', size: 12 },\n { id: 'topo-arrow', size: 16 },\n { id: 'topo-arrow-l', size: 22 },\n ].map(m => (\n <marker\n key={m.id}\n id={m.id}\n viewBox=\"0 0 10 10\"\n refX=\"8\"\n refY=\"5\"\n markerWidth={m.size}\n markerHeight={m.size}\n markerUnits=\"userSpaceOnUse\"\n orient=\"auto-start-reverse\"\n >\n <path d=\"M 0 0 L 10 5 L 0 10 z\" fill={pal.arrowFill} />\n </marker>\n ))}\n {/* Round 45: radial radar sweep gradient — bright at center\n (where it meets the hub) fading to leading edge. */}\n <radialGradient id=\"topo-sweep\" cx=\"0%\" cy=\"50%\" r=\"100%\">\n <stop offset=\"0%\" stopColor={isLight ? '#0d9488' : '#22d3ee'} stopOpacity={isLight ? 0.18 : 0.32} />\n <stop offset=\"70%\" stopColor={isLight ? '#0d9488' : '#22d3ee'} stopOpacity={isLight ? 0.10 : 0.18} />\n <stop offset=\"100%\" stopColor={isLight ? '#0d9488' : '#22d3ee'} stopOpacity=\"0\" />\n </radialGradient>\n </defs>\n\n {/* panel backdrop stays fixed — panning never reveals empty canvas */}\n <rect width=\"1000\" height=\"680\" fill=\"url(#topo-panel)\" />\n\n {/* Round 103 (issue #81): everything inside this <g> zooms + pans\n together. transform order = translate then scale.\n Round 168 / Loop: smoothView arms a one-shot transition on\n the transform attribute, active only when resetView/fitView\n fires. Pan (R103 pointer drag) and wheel zoom never set the\n flag, so they stay snappy with no lag. Pressing `0`, `f`,\n clicking the hub (R52), or chrome reset/fit buttons triggers\n a 300ms ease-out glide instead of a jolt. Respects prefers-\n reduced-motion via the R29 globals.css blanket override that\n neutralises transition-duration universally. */}\n <g\n transform={`translate(${view.x} ${view.y}) scale(${view.zoom})`}\n data-topo-viewport\n data-topo-viewport-smooth={smoothView ? 'true' : 'false'}\n data-topo-viewport-layout-switching={layoutSwitching ? 'true' : 'false'}\n data-topo-viewport-nodesize-switching={nodeSizeSwitching ? 'true' : 'false'}\n style={{\n // R170 / Loop: opacity always carries a transition so the\n // layout-switch crossfade fires cleanly. R168 smoothView\n // transition on transform is added only when armed (pan\n // and wheel zoom MUST stay snappy). The two arming flags\n // compose without conflict — different visual axes.\n // R171: nodeSizeSwitching shares the same opacity-dim\n // pathway as layoutSwitching — clicking S/M/L in the\n // chrome triggers the same soft-blink masking. ORing\n // both flags keeps the opacity expression simple while\n // the two distinct data-* attributes let tests\n // disambiguate which gesture armed the crossfade.\n transition: smoothView\n ? 'opacity 250ms ease-out, transform 300ms ease-out'\n : 'opacity 250ms ease-out',\n opacity: (layoutSwitching || nodeSizeSwitching) ? 0.45 : 1,\n }}\n >\n {/* Issue #87: radar/ring ambiance renders only in ring layout —\n grid mode drops it so the concentric rings don't sit behind a\n rectangular grid. */}\n {layout === 'ring' && (<>\n {/* R52: radar bg is pure decoration — drop its pointer events so\n the hub <g> under it (and any future inner-disk affordances)\n can receive clicks. Previously the r=330 disc intercepted\n the hub click outright. */}\n <circle cx={cx} cy={cy} r=\"330\" fill=\"url(#topo-radar)\" style={{ pointerEvents: 'none' }} />\n\n {/* Round 45: subtle star field — deterministic dots scattered\n across the canvas give the radar bg some depth. Skipped on\n light theme so the white surface stays clean.\n Round 291 / Loop: starfield dot count 28 → 14 (50%\n reduction). Post-R290 inner radar ring retirement the\n canvas has cleared meaningfully — sweep + 3 radar rings\n + tier guides + nodes + edges are doing the visual work.\n The starfield's role is atmospheric depth, not\n information; cutting density by half preserves the\n \"space/radar\" feel while removing decoration the eye\n has to skip. Same R275-R281 减法 family idiom as the\n orbit / halo / spoke retirements; same R290 pivot back\n to subtractive register. data-topo-starfield-dot\n attribute makes the dots probe-able for the regression\n test. */}\n {!isLight && (\n <g opacity=\"0.5\" style={{ pointerEvents: 'none' }} data-topo-starfield>\n {Array.from({ length: 14 }).map((_, i) => {\n // Deterministic pseudo-random scatter so positions are\n // stable between renders (no JS hydration mismatch).\n const seed = i * 9301 + 49297;\n const x = ((seed * 13) % 1000);\n const y = ((seed * 7) % 680);\n const r = (i % 3 === 0) ? 1.2 : 0.7;\n return <circle key={i} cx={x} cy={y} r={r} fill=\"#a5b4fc\" opacity={0.35 + (i % 4) * 0.05} data-topo-starfield-dot={i} />;\n })}\n </g>\n )}\n\n {/* Round 45: rotating radar sweep — a 40° wedge with a soft\n leading-edge gradient. Slow 6s rotation reads as a radar\n scan without being noisy. Inline transform-origin on the\n <g> wrapper ensures Chrome / Firefox rotate around (cx,cy)\n instead of the SVG viewBox corner.\n\n v0.10.0 Hero 3 Wave 1 / RFC §3.B (Vincent 5222 holdover):\n sweep arc retired. The diagonal rotating wedge competes\n with working-halo SMIL, hub busyness breath, and edge\n flow animation — on a 16:9 Twitter screenshot it reads\n 'wow lots of motion' rather than 'agents communicating'.\n Same idiom as R278/R279/R280 retirements — `false &&`\n short-circuits the IIFE so it's a one-line rollback. */}\n {false && (() => {\n // R146: radar sweep rotation buckets on workingCount, joining\n // R84 hub breath / R131 outer orbit / R132 group march /\n // R145 idle spokes as the 5th and final layer in the busyness-\n // driven motion family. 8 / 6 / 4 / 3 seconds — same 0 / 1-2 /\n // 3-5 / 6+ thresholds R84 uses. \"Busier fleet = more frequent\n // scans\" feels semantically right for a radar idiom. R45\n // baseline 6s sits at bucket 1 so the calm/busy spread bracks\n // around the historical default.\n const busy = workingCount === 0 ? 0\n : workingCount <= 2 ? 1\n : workingCount <= 5 ? 2\n : 3;\n const sweepDur = [8, 6, 4, 3][busy];\n return (\n <g\n style={{\n transformOrigin: `${cx}px ${cy}px`,\n transformBox: 'view-box',\n pointerEvents: 'none',\n // CSS var consumed by `.anet-topo-sweep` (line 848 of\n // globals.css). React's CSSProperties type doesn't model\n // custom properties → cast through Record<string, string>.\n ...({ ['--sweep-dur']: `${sweepDur}s` } as Record<string, string>),\n } as React.CSSProperties}\n className=\"anet-topo-sweep\"\n opacity={isLight ? 0.7 : 1}\n data-topo-sweep-bucket={busy}\n data-topo-sweep-dur={sweepDur}\n >\n <path\n d={`M ${cx} ${cy} L ${cx + 330} ${cy} A 330 330 0 0 0 ${cx + 330 * Math.cos(-Math.PI / 4.5)} ${cy + 330 * Math.sin(-Math.PI / 4.5)} Z`}\n fill=\"url(#topo-sweep)\"\n />\n </g>\n );\n })()}\n\n {/* radar rings — pure decoration at fixed radii, independent of\n node positions so the radar aesthetic is preserved across tier\n changes.\n Round 290 / Loop: drop the innermost radar ring at r=90.\n That ring sat ~66px outside the hub (hub radius 24, halo\n r=18), in the exact zone R276 (orbit particles) / R278\n (working halo) / R280 (backdrop spokes) cleared during\n the R275-R281 减法 arc. Post-cleanup the lone r=90 ring\n read as a leftover decorative loop hugging the hub — a\n visual element with no remaining sibling to anchor.\n Dropping it returns to the subtractive register after\n R282-R289's 8 加法 rounds and lets the hub breathe. The\n outer three rings (170 / 250 / 330) still carry the\n radar aesthetic across the canvas. New data-topo-radar-\n ring attribute exposes each remaining ring radius for\n test probing. */}\n {[170, 250, 330].map(radius => (\n <circle\n key={radius}\n cx={cx} cy={cy} r={radius}\n fill=\"none\" stroke={pal.ringStroke} strokeWidth=\"1\"\n opacity={isLight ? 0.6 : 0.35}\n data-topo-radar-ring={radius}\n />\n ))}\n\n {/* Round 54 / Loop: tier-radius guide rings. The radar rings above\n are decorative and don't match the actual tier radii nodes sit\n on (single 220 / dual 175,260 / triple 145,215,285). Drawing\n a faint dashed ring at each ACTIVE tier radius lets the eye\n anchor \"this is the inner / outer ring\" without inferring from\n node spacing. Picked based on online node count so only the\n tiers currently in use draw — empty tiers stay quiet. pointer-\n events:none so they never intercept hub or node clicks. The\n 0.7 stroke + dashed pattern reads as guide, not feature. */}\n {(() => {\n const tierRadii = onlineNodes.length > onlineTripleThreshold\n ? [onlineTripleInnerR, onlineTripleMidR, onlineTripleOuterR]\n : onlineNodes.length > onlineTierThreshold\n ? [onlineInnerRadius, onlineOuterRadius]\n : onlineNodes.length > 0\n ? [onlineRadius]\n : [];\n // Round 92 / Loop: tier-ring occupancy. R54 drew the guide\n // rings at fixed opacity regardless of how many nodes lived\n // on each tier. With pinned filters dimming most nodes to\n // 0.28, the ring at a deserted tier looked identical to a\n // crowded one — wasting a free piece of canvas. Count\n // online nodes whose hub-distance falls within ±15 px of\n // each ring (15 px = half the inter-tier gap, so each\n // node is assigned to exactly one ring). Empty tier → skip\n // entirely. Crowded tier → stronger opacity, says \"look\n // here\". Buckets so the ladder feels intentional, not\n // jittery as one node migrates between tiers.\n const occupancyOf = (r: number) => onlineNodes.reduce((acc, s) => {\n const p = nodePositions[s.alias];\n if (!p) return acc;\n const d = Math.hypot(p.x - cx, p.y - cy);\n return Math.abs(d - r) < 15 ? acc + 1 : acc;\n }, 0);\n // Round 93 / Loop: when any pin is active, tint the tier\n // rings to the legend accent so the spatial guide visually\n // shares the canvas's \"filtered mode\" colour. The chip\n // row already says WHICH filter is on (pills + letter\n // mirror); rings answering \"we're filtered\" reinforces\n // the state when the eye is on the canvas, not the chip\n // row. Composes cleanly with R92 occupancy — same opacity\n // bucket logic; just the stroke colour swaps.\n const anyPin = !!(pinnedStatus || pinnedGroup || pinnedVendor);\n const tierStroke = anyPin ? pal.legendAccent : pal.ringStroke;\n return tierRadii.map((r, tierIdx) => {\n const n = occupancyOf(r);\n if (n === 0) return null;\n const bucket = n <= 2 ? 0 : n <= 6 ? 1 : 2;\n const opLight = [0.24, 0.36, 0.50][bucket];\n const opDark = [0.32, 0.46, 0.62][bucket];\n // Round 174 / Loop: tier guide rings fade-in alongside\n // the R9/R72/R172/R173 first-paint wave. Ring layout's\n // structural scaffolding (R54 dashed concentric guides)\n // was the last instant-pop element after R173 closed\n // group boxes in grid. Same vocabulary — .anet-fade-in\n // mount-once CSS animation + per-ring stagger 60ms ×\n // tierIdx (cap 8). Tier rings are at most 3 (single /\n // dual / triple), so the visible range is 0-120ms.\n // Inner ring leads outward — emanates from the hub.\n // transition list grows `opacity 250ms ease-out` so the\n // post-animation snap from animation's end state (1) to\n // the bucket opacity (0.24-0.62) eases instead of cuts.\n // Same pattern node fade-in uses (R9 + transition-opacity\n // from R3 className). data-tier-fade-delay exposes the\n // computed delay for test probing.\n const fadeDelay = Math.min(tierIdx, 8) * 60;\n return (\n <circle\n key={`tier-${r}`}\n cx={cx} cy={cy} r={r}\n fill=\"none\"\n stroke={tierStroke}\n strokeWidth=\"0.7\"\n /* Round 303 / Loop: tier guide dashes tighten from\n \"2 8\" → \"2 6\" (8px gap → 6px gap). R54 set \"2 8\"\n to read as a faint hint behind everything else;\n after R290 (inner radar ring retired) + R291\n (starfield 50%) cleared the surrounding backdrop\n density, the tier guides carry more \"this is the\n ring nodes sit on\" visual responsibility. Pulling\n the gap from 8→6 puts dashes closer together so\n the ring reads as a clearer continuous mark\n rather than scattered dots, without bumping\n strokeWidth (0.7) or opacity (R92 bucketed). The\n 2px dash itself unchanged — same density signal\n per dash, just fewer-px space between them. */\n strokeDasharray=\"2 6\"\n opacity={isLight ? opLight : opDark}\n className=\"anet-fade-in\"\n style={{\n pointerEvents: 'none',\n transition: 'stroke 200ms ease-out, opacity 250ms ease-out',\n animationDelay: `${fadeDelay}ms`,\n }}\n data-tier-ring={r}\n data-tier-occupancy={n}\n data-tier-bucket={bucket}\n data-tier-tinted={anyPin ? 'true' : 'false'}\n data-tier-fade-delay={fadeDelay}\n />\n );\n });\n })()}\n\n {/* Round 50: 4 small particles slowly orbiting the outer ring\n (r=330). Each starts at a different angle (offset 0/0.25/0.5/0.75\n of the cycle) so they're evenly spaced. 16s per revolution is\n slow enough to feel ambient, not noisy. Skipped on light theme\n so the white surface stays clean.\n\n R131 / Loop: orbit period now buckets on workingCount,\n mirroring R84's hub-busyness breath cadence. An idle\n fleet keeps the original 16s \"calm sweep\"; as work\n accumulates the orbit subtly accelerates (capped at\n 10s so it never feels frantic). Two-layer motion\n coordination now: R84 breathes the hub, R131 spins\n the outer ring — both reading the same underlying\n \"is the network busy\" signal, both visible\n simultaneously without competing. Same bucket\n thresholds (0 / 1-2 / 3-5 / 6+) the R84 block uses\n at line ~2702 so the two cadences stay in sync if\n a future refactor shifts buckets.\n\n Round 276 / Loop: orbit particles DISABLED by default\n per Vincent 5214/5215-5217 visual-audit relay\n (clutter cleanup for Twitter screenshot). The 4\n particles encode workingCount busyness via speed\n (R131) + opacity (R216) — but that signal is\n ALREADY conveyed by:\n · hub halo opacity breath (R244, R84)\n · hub digit workingCount text (R130)\n · pressure-bar working/idle/offline ratio (R31)\n So orbit particles are info-redundant decoration:\n they don't add new signal, just add visual noise at\n the canvas outer edge. R276 gates the render block\n with `false &&` so the code stays (commented context\n + tests preserved for hypothetical rollback) but\n nothing renders. R131 busy-bucket constant + R216\n opacity-bucket constant are dead code post-R276 —\n acceptable since the family was R50/R131/R216 and\n R276 retires the family entirely. Net: 4 fewer\n moving dots on canvas. */}\n {false && !isLight && (() => {\n const busy = workingCount === 0 ? 0\n : workingCount <= 2 ? 1\n : workingCount <= 5 ? 2\n : 3;\n const dur = [16, 14, 12, 10][busy];\n // Round 216 / Loop: orbit particle opacity scales with busy\n // bucket alongside R131's speed scaling. Pre-R216 the\n // particles sat at flat opacity 0.9 regardless of fleet\n // workload — speed conveyed busyness but brightness was\n // mute. R216 layers brightness on top of speed so idle\n // fleets read calm (dim particles) and busy fleets read\n // bright (loud particles). Same R84 hub-breath /\n // R131 orbit-speed bucket thresholds (0 / 1-2 / 3-5 / 6+)\n // so the three motion layers (hub breath / orbit / group\n // march) plus the new brightness channel all derive from\n // one busyness metric. transition: opacity 300ms ease-out\n // matches R167 status-flip + R213 hub crossfade timing —\n // when first working node appears, hub focal point AND\n // outer ring particles brighten on the same 300ms beat.\n const orbitOpacity = [0.5, 0.7, 0.85, 1.0][busy];\n return [0, 0.25, 0.5, 0.75].map((phase, i) => (\n <g\n key={`orbit-${i}`}\n data-topo-orbit-bucket={busy}\n data-topo-orbit-dur={dur}\n data-topo-orbit-opacity={orbitOpacity}\n >\n <circle\n cx={cx + 330} cy={cy}\n r={i === 0 ? 2.8 : 2.2}\n fill=\"#22d3ee\"\n opacity={orbitOpacity}\n filter=\"url(#topo-glow)\"\n style={{ transition: 'opacity 300ms ease-out' }}\n >\n <animateTransform\n attributeName=\"transform\"\n type=\"rotate\"\n from={`${phase * 360} ${cx} ${cy}`}\n to={`${phase * 360 + 360} ${cx} ${cy}`}\n dur={`${dur}s`}\n repeatCount=\"indefinite\"\n />\n </circle>\n </g>\n ));\n })()}\n\n {/* Round 240 / Loop: extend R93 anyPin tinting from tier-rings\n to backdrop spokes. Pre-R240 the 6 radar-style spokes\n stayed at pal.ringStroke regardless of filter state,\n while R93 already shifted tier-rings to pal.legendAccent\n on any active pin. Result: ring scaffolding said\n 'filtered mode' but spoke scaffolding said 'rest' — the\n two halves of the canvas's spatial guide were out of\n sync. R240 ties them together; whole backdrop now reads\n as one filtered-mode-coloured unit when a pin is active.\n\n Same anyPin signal R93 uses (pinnedStatus || pinnedGroup\n || pinnedVendor). Same legendAccent tint colour. Same\n 200ms ease-out transition timing — pin a status, both\n tier-rings AND spokes ease to cyan together. */}\n {/* Round 280 / Loop: backdrop spokes RETIRED (R93 family with\n R240 tinting) per 减法 cut #6. The 6 radial lines at\n every 30° formed 12 rays from canvas center — even at\n opacity 0.18 (cyber) / 0.35 (light) they added explicit\n radial-line clutter behind the hub-and-spoke topology.\n The radial-gradient backdrop (topo-radar) ALREADY\n provides soft hub-centered glow; explicit lines on top\n were decorative density without structural signal that\n the topology itself doesn't already convey (hub at\n center + nodes on rings = radial structure inherent).\n `false &&` gates the render; code preserved for\n rollback. Same idiom as R276 orbit / R278 working halo\n / R279 ping+pulse retirements. */}\n {false && (() => {\n const anyPin = !!(pinnedStatus || pinnedGroup || pinnedVendor);\n const spokeStroke = anyPin ? pal.legendAccent : pal.ringStroke;\n return [0, 30, 60, 90, 120, 150].map(angle => (\n <line\n key={angle}\n x1={cx - 360 * Math.cos(angle * Math.PI / 180)}\n y1={cy - 360 * Math.sin(angle * Math.PI / 180)}\n x2={cx + 360 * Math.cos(angle * Math.PI / 180)}\n y2={cy + 360 * Math.sin(angle * Math.PI / 180)}\n stroke={spokeStroke}\n strokeWidth=\"1\"\n opacity={isLight ? 0.35 : 0.18}\n data-topo-spoke-angle={angle}\n data-topo-spoke-tinted={anyPin ? 'true' : 'false'}\n style={{ transition: 'stroke 200ms ease-out' }}\n />\n ));\n })()}\n </>)}\n\n {/* hub links — round 46: idle spokes now have animated\n stroke-dashoffset so dashes flow outward from the hub\n (\"command relay\" feel). Active spokes carrying live message\n flow stay as solid bright strokes.\n R145 / Loop: idle-spoke animation cadence buckets on\n workingCount, mirroring R84 hub-breath / R131 outer-\n ring orbit / R132 groupbox-march coordination. Same\n 0 / 1-2 / 3-5 / 6+ thresholds. Idle ↔ idle network\n has a slow \"command relay\" feel; as work accumulates\n the dashes accelerate outward, completing the 4th\n motion layer in the busyness-driven family:\n R84 hub breath (centre)\n R131 outer ring orbit (periphery)\n R132 groupbox march (per-team, grid layout)\n R145 idle-spoke flow (ring layout, hub→nodes)\n 4 surfaces, 1 signal. Same cadence ladder 2.8/2.4/2.0/\n 1.6s — 1.75× range from calm to busy, capped so the\n network never feels frantic. */}\n {layout === 'ring' && (() => {\n const busy = workingCount === 0 ? 0\n : workingCount <= 2 ? 1\n : workingCount <= 5 ? 2\n : 3;\n const spokeDur = [2.8, 2.4, 2.0, 1.6][busy];\n return onlineNodes.map((session, idx) => {\n const pos = nodePositions[session.alias];\n if (!pos) return null;\n const path = curvePath({ x: cx, y: cy }, pos, 0);\n const isActiveSpoke = activeAliases.has(session.alias);\n\n /* Round 241 / Loop: hub-link spokes (agent→hub paths in\n ring layout) eased state-flip between idle and active.\n Pre-R241 when a node sent or received a message its\n hub-spoke jumped one-frame from idle gray (pal.spoke-\n Stroke.idle + strokeWidth=1 + opacity=0.45) to active\n cyan (pal.spokeStroke.active + strokeWidth=2 + opacity\n =0.7) — three discrete property snaps in lockstep.\n R241 adds a 250ms ease-out transition list covering\n stroke + stroke-width + opacity so the activation\n 'lights up' smoothly. strokeDasharray stays binary\n (none ↔ '6 14') — dasharray doesn't interpolate\n cleanly between continuous and discrete forms across\n browsers (same lesson R167 documented for the node\n status ring). The CSS keyframe animation on idle\n spokes (anet-topo-spoke-flow) drives stroke-dashoffset\n separately and stays untouched. data-topo-hub-spoke-\n active surfaces the activity state for test probes\n (active spokes don't carry the bucket/dur attrs so\n they need their own data anchor). */\n return (\n <path\n key={`hub-${session.alias}`}\n d={path}\n fill=\"none\"\n stroke={isActiveSpoke ? pal.spokeStroke.active : pal.spokeStroke.idle}\n strokeWidth={isActiveSpoke ? 2 : 1}\n strokeDasharray={isActiveSpoke ? 'none' : '6 14'}\n opacity={isActiveSpoke ? 0.7 : 0.45}\n className={isActiveSpoke ? undefined : 'anet-topo-spoke-flow'}\n data-topo-spoke-bucket={isActiveSpoke ? undefined : busy}\n data-topo-spoke-dur={isActiveSpoke ? undefined : spokeDur}\n data-topo-hub-spoke-active={isActiveSpoke ? 'true' : 'false'}\n style={{\n transition: 'stroke 250ms ease-out, stroke-width 250ms ease-out, opacity 250ms ease-out',\n ...(isActiveSpoke ? {} : {\n animationDelay: `${-(idx * 0.25)}s`,\n // CSS var consumed by `.anet-topo-spoke-flow`\n // (line 859 of globals.css). React's CSSProperties\n // type doesn't model custom properties → cast\n // through Record<string, string>.\n ...({ ['--spoke-dur']: `${spokeDur}s` } as Record<string, string>),\n }),\n } as React.CSSProperties}\n />\n );\n });\n })()}\n\n {/* #111: prefix-group boundary boxes (Vincent 4722). Grid layout\n only — groupBoxes is empty in ring mode. Rendered behind the\n flow links + nodes; pointer-events off so they never intercept\n a node click. Restrained dashed container + group-name label. */}\n {groupBoxes.map((box, boxIdx) => {\n const isHovered = activeGroup === box.key;\n // R68: distinguish \"locked by click\" from \"currently hovered\".\n // R63 made pinned and hovered identical (both hit isHovered\n // via activeGroup). A user with one team pinned should see at\n // a glance which is the locked one even while their cursor\n // sweeps elsewhere. isPinned reads pinnedGroup directly\n // (NOT activeGroup) so the visual is specific to the sticky\n // state — transient hover keeps the R63 isHovered styling.\n const isPinned = pinnedGroup === box.key;\n // Round 18 / Loop: group-box hover linkage. The Round 8 fade\n // already dropped OUT-of-focus groups to 0.28, but the IN-focus\n // group sat at its baseline appearance — no positive emphasis.\n // Hovering now upgrades the box to an \"accent\" treatment:\n // solid stroke (not dashed), thicker, accent-coloured; brighter\n // text and slightly stronger fill. Label and box read as one\n // selected unit. Geometry unchanged → overlap test untouched.\n // R132: per-group marching-ants duration computed once,\n // reused on the data attribute + the inline custom property.\n const w = box.statuses.working;\n const marchDur = w >= 6 ? 8 : w >= 4 ? 10 : w >= 2 ? 12 : 14;\n return (\n <g\n key={`grp-${box.key}`}\n data-group={box.key}\n // Round 173 / Loop: group boxes pick up the first-paint\n // fade-in wave alongside R9 staggered nodes (0-540ms)\n // and R172 staggered edges (280-980ms). Pre-R173 the\n // structural box frames appeared instantly while the\n // nodes inside eased in — the reveal felt like\n // 'frame slams down, nodes drift in'. The .anet-fade-in\n // CSS animation (R3 origin, 0.15s ease-out, mount-once)\n // plays alongside the node R9 stagger; each box offset\n // by boxIdx × 60ms (cap at 8 indices so a fleet with\n // many groups still finishes within ~500ms) so boxes\n // appear like a soft sweep across the grid rather than\n // a single pop. animation-fill-mode default 'none' →\n // post-animation control reverts to the inline opacity\n // style below (1 / 0.28 based on filter pin state).\n // data-group-fade-delay exposes the computed delay for\n // test probes.\n className=\"transition-opacity anet-fade-in\"\n data-group-fade-delay={Math.min(boxIdx, 8) * 60}\n // R63: drop the blanket pointerEvents:'none' that\n // previously sat here. Chrome's SVG impl doesn't let a\n // child override a parent's `none` even though the spec\n // says it should — moving the property onto just the\n // rect (where it's needed so nodes underneath stay\n // clickable) lets the label text receive its own click.\n style={{\n opacity: !activeGroup || isHovered ? 1 : 0.28,\n animationDelay: `${Math.min(boxIdx, 8) * 60}ms`,\n }}\n >\n <rect\n x={box.x}\n y={box.y}\n width={box.w}\n height={box.h}\n rx=\"14\"\n fill={isLight ? '#0f172a' : '#a5b4fc'}\n // R68: 3-tier opacity + stroke ladder.\n // pinned → fill 0.08 / 0.13, stroke 3 px (locked)\n // hovered → fill 0.05 / 0.09, stroke 2 px (inspecting)\n // idle → fill 0.025 / 0.045, stroke 1.5 px dashed\n fillOpacity={isPinned ? (isLight ? 0.08 : 0.13)\n : isHovered ? (isLight ? 0.05 : 0.09)\n : (isLight ? 0.025 : 0.045)}\n stroke={(isPinned || isHovered) ? pal.legendAccent : pal.ringStroke}\n strokeWidth={isPinned ? 3 : isHovered ? 2 : 1.5}\n strokeDasharray={(isPinned || isHovered) ? 'none' : '6 6'}\n data-group-box-pinned={isPinned ? 'true' : 'false'}\n // R85: ambient \"marching ants\" drift on the perimeter\n // when this group has at least one working member, and\n // neither pin nor hover is active (those treatments\n // already shout for attention via solid stroke). 12s\n // cycle reads as ambient — the eye parses \"live work\n // here\" without registering the box as animating.\n // R132: per-group ant rate buckets on box.statuses.working\n // — the same coupling-to-busyness idiom R84 uses for the\n // hub and R131 uses for the outer-ring orbit, applied at\n // the GROUP scale. A team with one working member ambles;\n // a team with five working members visibly accelerates.\n // Bucket boundaries (1 / 2-3 / 4-5 / 6+) chosen to land\n // on the same 14/12/10/8 cadence ladder so the three\n // motion layers (hub / ring / group) keep a coherent\n // tempo grammar. Default 12s when working=0 doesn't\n // matter — the className is only applied when working>0.\n data-group-box-live={!isPinned && !isHovered && box.statuses.working > 0 ? 'true' : 'false'}\n data-group-box-march-dur={marchDur}\n data-group-box-lifted={(isPinned || isHovered) ? 'true' : 'false'}\n className={!isPinned && !isHovered && box.statuses.working > 0 ? 'anet-topo-groupbox-live' : undefined}\n // R142: drop-shadow filter when pinned or hovered. Box\n // visually \"rises off the canvas\" — same vocabulary\n // R18 KPI cards + R135 overlay panels use. Idle group\n // boxes carry no filter (purely flat dashed outline)\n // so the unstyled canvas stays uncluttered. Filter\n // affects paint area only, not the geometric bbox\n // the overlap-test reads, so zero-overlap invariant\n // is preserved.\n filter={(isPinned || isHovered) ? 'url(#topo-groupbox-lift)' : undefined}\n style={{\n /* Round 248 / Loop: append fill 200ms ease-out to\n the existing R66 transition list. Pre-R248 the\n rect's fill (isLight ? '#0f172a' (slate-900) :\n '#a5b4fc' (indigo-300)) snapped on theme toggle\n while stroke / fill-opacity / filter all eased.\n Closes the last theme-toggle snap on the group\n box surface — same idiom R246 + R247 used at\n per-node label-card and side-panel scopes. */\n transition: 'stroke 200ms ease-out, stroke-width 200ms ease-out, fill-opacity 200ms ease-out, filter 200ms ease-out, fill 200ms ease-out',\n pointerEvents: 'none',\n // CSS var consumed by `.anet-topo-groupbox-live`\n // (line 877 of globals.css). React's CSSProperties\n // type doesn't model custom properties, so cast\n // through Record<string, string>.\n ...({['--march-dur']: `${marchDur}s`} as Record<string, string>),\n }}\n />\n {/* R63: wrap label in a clickable <g> with an invisible\n rect hitbox. The text alone wasn't getting hit-tested\n reliably — the SVG-wide topo-panel <rect> intercepts\n at the label's screen position when the label sits at\n a high viewBox-y (it lands below where the compositor\n expects the text to paint on top, same gotcha as the\n recent-signal panel rows in R56). Hitbox rect + the\n parent <g> taking the click + onPointerDown stop-\n propagation match the R55/R56/R61 pattern. */}\n <g\n role=\"button\"\n tabIndex={0}\n aria-pressed={pinnedGroup === box.key}\n data-group-label-hit={box.key}\n className=\"anet-topo-svg-focus\"\n style={{ pointerEvents: 'all', cursor: 'pointer' }}\n onPointerDown={(e) => e.stopPropagation()}\n onClick={() => setPinnedGroup(prev => prev === box.key ? null : box.key)}\n // R86: hover the label → transient group focus. Releases\n // on leave; activeGroup = hoveredGroup ?? pinnedGroup\n // formula already handles transient-over-pin so a\n // user can spot-compare teams without losing their\n // pinned one. Closes the same R83-style hover/click\n // gap (segments) — now group labels carry it too.\n onPointerEnter={() => setHoveredGroupLabel(box.key)}\n onPointerLeave={() => setHoveredGroupLabel(prev => prev === box.key ? null : prev)}\n // R152: a11y completeness — R63 added role + tabIndex +\n // aria-pressed but never wired onKeyDown, so the focused\n // group label was tab-reachable but Enter/Space was a\n // no-op. Closes the last keyboard gap among the\n // role=\"button\" surfaces. Other group-pin trigger paths\n // (R69 palette, R74 cmdk, R86 hover, dispatchEvent) are\n // unchanged. Matches the onKeyDown idiom from R116 /\n // R139 / R140 / R151 (Enter & Space → same setter as\n // onClick, preventDefault on Space to stop SVG scroll).\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setPinnedGroup(prev => prev === box.key ? null : box.key);\n }\n }}\n >\n {/* R99: SVG <title> tooltip listing group members +\n status breakdown. Same info-density spirit as\n R97 pill tooltips + R98 node tooltips — anywhere\n a UI element says \"alpha · 3\" should hover-\n explain WHICH 3. Truncates at 8 aliases with a\n \"+N more\" suffix so a 20-member band doesn't\n paint a 22-line tooltip. */}\n {(() => {\n const members = Object.entries(groupKeys)\n .filter(([, key]) => key === box.key)\n .map(([alias]) => alias);\n const memberPreview = members.slice(0, 8).join(', ');\n const suffix = members.length > 8 ? ` + ${members.length - 8} more` : '';\n const statusSummary = [\n box.statuses.working > 0 ? `${box.statuses.working} working` : null,\n box.statuses.idle > 0 ? `${box.statuses.idle} idle` : null,\n box.statuses.offline > 0 ? `${box.statuses.offline} offline` : null,\n ].filter(Boolean).join(' · ');\n return (\n <title>{[\n `${box.key} (${members.length} member${members.length === 1 ? '' : 's'})`,\n statusSummary || null,\n `${memberPreview}${suffix}`,\n pinnedGroup === box.key ? 'click to release pin' : 'click to pin this group',\n ].filter(Boolean).join('\\n')}</title>\n );\n })()}\n <rect\n x={box.x + 6}\n y={box.y + 2}\n width={Math.min(box.w - 12, 240)}\n height={20}\n rx=\"4\"\n /* R107 / Loop: list-item tint extends to the SVG\n group labels — same idiom R104 added to recent-\n signal rows and R105 to the legend rows. The\n tint colour is pal.legendAccent (cyan) since\n groups don't carry an inherent swatch the way\n legend rows do; this matches R68's group-box\n isPinned/isHovered accent stroke for consistency.\n hover < pinned opacity so locked vs preview is\n discriminable at a glance. */\n fill={pinnedGroup === box.key || hoveredGroupLabel === box.key ? pal.legendAccent : 'transparent'}\n opacity={pinnedGroup === box.key ? (isLight ? 0.16 : 0.20)\n : hoveredGroupLabel === box.key ? (isLight ? 0.09 : 0.13)\n : 1}\n data-group-label-tinted={pinnedGroup === box.key ? 'pinned' : hoveredGroupLabel === box.key ? 'hover' : 'none'}\n style={{ transition: 'fill 150ms ease-out, opacity 150ms ease-out' }}\n />\n {/* Round 218 / Loop: group label gains a letter-spacing\n transition on pin — the text subtly spaces out\n (0px → 0.5px) when the group is locked, giving the\n pinned state its own typographic signature distinct\n from R63's transient hover fill brighten. Hover and\n pin share the same fill colour (legendHeadline), so\n pre-R218 the only thing distinguishing them was\n R142 drop-shadow + R68 rect stroke. R218 adds a\n type-level signal: pinned text spreads slightly,\n feels \"locked in\" / \"open and held\". Letter-\n spacing is one of the few SVG text properties that\n interpolates smoothly across the major browsers.\n Hover stays at default tracking — the spread is\n pin-exclusive so users can read pinned vs\n hovered at the text alone. transition 200ms\n matches R142 fill timing so all the group-label\n state-flip channels (fill colour, rect stroke,\n rect drop-shadow, label tracking) ease as one. */}\n <text\n x={box.x + 12}\n y={box.y + 14}\n fill={isHovered ? pal.legendHeadline : pal.legendText}\n fontSize=\"13\"\n fontFamily=\"monospace\"\n fontWeight=\"700\"\n style={{\n transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out',\n letterSpacing: isPinned ? '0.5px' : '0px',\n }}\n data-group-label={box.key}\n data-group-label-pinned={isPinned ? 'true' : 'false'}\n >\n {box.key}\n {/* Round 19 / Loop: member-count chip. Inline tspan stays\n in the single <text> bbox the overlap test reads, so\n the node↔label guard still catches if the chip ever\n pushes the label far enough right to clip a node.\n Smaller + lighter weight reads as metadata, not name. */}\n {/* Round 229 / Loop: member-count chip drops its explicit\n fill so it inherits from the parent <text>, which means\n R142's hover-fill transition (legendText → legend-\n Headline, 200ms ease-out) NOW carries the count chip\n with it. Pre-R229 the parent name brightened on\n hover while the count tspan stayed at legendText —\n \"name lit, count dimmer than at rest\" inverted the\n tonal hierarchy. Inheriting matches the name's\n transition; both rest and hover keep the SAME\n tonal relationship between name and count.\n 7th surface in the hover-deepen-own-hue family\n (legend rows, chip-row counts, status pip, recent\n row text, pressure-bar segments, group-box fill +\n this round's group-label-count chip).\n\n Also picks up tabular-nums (5th surface in the\n info-density tabular-nums sweep after R224 edge\n badge / R225 hub digit / R225 panel header /\n R225 recent row count). The member count rolls\n over often (4→5→…→9→10 as a group grows) and\n lives at a fixed dx=6 offset from the name, so a\n digit-width jitter at 9→10 used to shift the\n whole count visibly. Tabular locks it. */}\n <tspan\n dx=\"6\"\n fontSize=\"11\"\n fontWeight=\"400\"\n data-group-label-count={box.key}\n data-group-label-count-value={box.count}\n style={{ fontVariantNumeric: 'tabular-nums' }}\n >· {box.count}</tspan>\n {/* Round 58 / Loop: status mix pip strip. Compact text-\n based chips (e.g. \"2w 1i\") so the strip stays inside\n the same <text> bbox the overlap-test reads — keeps\n the R27 label↔label and R19 node↔label guards intact.\n Each tier is colour-coded against the legend swatches\n and only renders when count > 0, so a healthy all-\n working group reads simply \" · 2w\".\n\n Round 207 / Loop: each tspan eases in on mount\n via anet-fade-in. Pre-R207 when a group's first\n working node went idle (or first idle node went\n working), the new tier's tspan snap-popped into\n the label. Same snap-on-mount issue R203 fixed\n for recent-signal rows, applied at the group-\n label scope. Each tier is keyed on its boolean\n mount, so the animation fires once when the\n tspan first appears (count crosses 0 → 1+),\n not on every count update (e.g., 1 → 2 working\n preserves the tspan via React reconciliation).\n Exit remains snap — matches R190's \"fade-IN\n smooth, accept exit snap\" trade-off used for\n the R129 hot-tail. */}\n {/* Round 230 / Loop: tabular-nums on the 3 status pips\n so the count digit doesn't jitter the adjacent\n pip when a tier crosses 9 → 10. The pips render\n in sequence at dx=8/4/4 — width-shift on any\n tier propagates rightward through the strip,\n visibly compressing or stretching the gap\n between adjacent tier chips. Tabular locks the\n digit so the strip stays stable as tiers grow.\n 6th surface in the info-density tabular-nums\n sweep after R224 edge badge / R225 hub digit /\n R225 panel header / R225 recent row count /\n R229 group-label count. Tier-specific fill\n colours stay (semantic — working green /\n idle teal / offline slate). */}\n {/* Round 253 / Loop: append fill 200ms ease-out to\n each tspan's style so theme toggle eases the\n tier-coloured pips alongside every other\n theme-driven element. R230's tabular-nums\n stays. */}\n {/* Round 319 / Loop: drop a tier pip when its count\n equals box.count — i.e. single-tier groups (all\n working, all idle, all offline). Pre-R319 a 4-all-\n idle group rendered as `P站 · 4 4i` with the \"4\"\n visually doubled; Vincent telegram 5304 flagged\n this as 比较难看 in a real-data screenshot\n (ai-insight · 6 6i, blueleap · 3 3i, P站 · 4 4i).\n The dropped pip's information is already conveyed\n by the group-box stroke colour (R68 isPinned/\n hover accent uses the dominant-tier hue) plus\n the SVG <title> tooltip listing the status\n breakdown. Multi-tier groups (e.g. `alpha · 3\n 2w 1i`) render unchanged — those pips genuinely\n add breakdown info that the total doesn't carry. */}\n {box.statuses.working > 0 && box.statuses.working !== box.count && (\n <tspan\n dx=\"8\"\n fill={isLight ? '#059669' : '#22c55e'}\n fontSize=\"11\"\n fontWeight=\"600\"\n className=\"anet-fade-in\"\n data-group-pip=\"working\"\n style={{ fontVariantNumeric: 'tabular-nums', transition: 'fill 200ms ease-out' }}\n >{box.statuses.working}w</tspan>\n )}\n {box.statuses.idle > 0 && box.statuses.idle !== box.count && (\n <tspan\n dx=\"4\"\n fill={isLight ? '#0d9488' : '#2dd4bf'}\n fontSize=\"11\"\n fontWeight=\"600\"\n className=\"anet-fade-in\"\n data-group-pip=\"idle\"\n style={{ fontVariantNumeric: 'tabular-nums', transition: 'fill 200ms ease-out' }}\n >{box.statuses.idle}i</tspan>\n )}\n {box.statuses.offline > 0 && box.statuses.offline !== box.count && (\n <tspan\n dx=\"4\"\n fill={isLight ? '#94a3b8' : '#6b7280'}\n fontSize=\"11\"\n fontWeight=\"600\"\n className=\"anet-fade-in\"\n data-group-pip=\"offline\"\n style={{ fontVariantNumeric: 'tabular-nums', transition: 'fill 200ms ease-out' }}\n >{box.statuses.offline}o</tspan>\n )}\n </text>\n </g>\n </g>\n );\n })}\n\n {/* directed message flows */}\n {flowLinks.map((link, index) => {\n const from = nodePositions[link.from];\n const to = nodePositions[link.to];\n if (!from || !to) return null;\n\n // Round 7 / Loop: lift now scales with distance so short links\n // aren't over-bent (long links keep the ~36px hump), and the\n // particle period shortens with link.count so busier edges\n // visibly pulse faster — instant info density on top of the\n // existing stroke-width-by-count chip.\n const dist = Math.hypot(to.x - from.x, to.y - from.y);\n const lift = (index % 2 === 0 ? 1 : -1) * Math.min(36, dist * 0.18);\n const path = curvePath(from, to, lift);\n const width = Math.min(2 + link.count, 7);\n const duration = Math.max(0.9, 2.6 / Math.sqrt(link.count));\n // Round 231 / Loop: per-edge phase stagger lifted into a\n // named constant so the R75 arrival ping + R76 dispatch\n // pulse SMIL animates can RE-COUPLE to the R103 particle's\n // cycle. Pre-R103 (when particle started at phase 0) the\n // ping fired at \"near end of cycle\" (-0.92*dur) and dispatch\n // at \"cycle start\" (0) — both phase-coincident with particle\n // arrival/departure respectively. R103's golden-ratio\n // stagger broke that coupling — particle now started at\n // phase (index*0.37)%dur while ping+pulse stayed at fixed\n // offsets, so the rings fired at random moments relative\n // to particle position. R231 expresses dispatch_begin and\n // arrival_begin in terms of THIS stagger so they fire\n // exactly when the particle is at source / near destination\n // respectively — restoring R75/R76's original semantic\n // and unifying the three SMIL elements into one\n // synchronised per-edge animation set.\n const stagger = (index * 0.37) % duration;\n // Round 10 / Loop: freshness fade. An edge that fired ≤30s ago\n // stays at full intensity; over 5 minutes it decays to ~35%.\n // Surfaces \"what's happening now\" vs background chatter without\n // hiding old flow entirely (some context still useful). `now`\n // captured at useMemo-recompute time (every 5s message refresh)\n // — accuracy is within the poll interval, plenty.\n const ageMs = link.last_at ? Math.max(0, Date.now() - Date.parse(link.last_at)) : 0;\n const fresh = Math.max(0.35, 1 - ageMs / (5 * 60 * 1000));\n // Round 16 arrow-tier binning — keep `topo-arrow` as the\n // medium tier id so the legend swatch picks it up unchanged.\n const arrowId = link.count <= 2 ? 'topo-arrow-s'\n : link.count <= 4 ? 'topo-arrow'\n : 'topo-arrow-l';\n\n // Round 39 / Loop: edge hover tooltip — surface the same\n // last_at + count info the freshness fade and arrow tier\n // already encode visually, in plain text. The stroke is the\n // hover target; SVG `<title>` honours newlines on every\n // browser the dashboard targets.\n const lastAt = relativeAgo(link.last_at);\n const tooltip = `${link.from} → ${link.to}\\n${link.count} message${link.count === 1 ? '' : 's'}${lastAt ? ` · last ${lastAt}` : ''}`;\n // Round 40 / Loop: edges follow node hover — when an alias is\n // hovered, every edge touching it brightens, the rest fade.\n // Pairs with the Round 8 group-focus fade on nodes: hover a\n // node to find \"who is this agent talking to\" at a glance.\n // No hover → multiplier is 1.0 (current behaviour preserved).\n // Round 50 / Loop: edge-on-self priority. When the user hovers\n // a flow edge directly (R48 widened the hitbox so this is now\n // a precise gesture), THAT edge gets the strongest boost (2.0)\n // and every other edge dims to 0.35. Node-hover (R40) keeps\n // its own ladder. Edge-hover and node-hover are mutually\n // exclusive in practice — the cursor is over one or the\n // other — but the order below makes edge-hover win if both\n // ever read truthy at the same React tick.\n // Round 53 / Loop: in-group edges follow team focus. R40\n // brightened only edges touching the exact hovered alias —\n // but with R8/R18 prefix-clustering, a user hovering one\n // member is asking about the team. So when BOTH endpoints of\n // an edge share the hoveredGroup (and neither is the exact\n // hovered alias — that gets the stronger 1.7×), the edge\n // boosts to 1.3×. Edges leaving the team (one endpoint in,\n // one out) still dim to 0.35× per R40 since they read as\n // background to the focus. Singletons fall through unchanged\n // (their group key is the alias itself, so bothInHoveredGroup\n // is impossible for a non-self-edge).\n // R116: composes hover ?? pin — a pinned edge stays \"hot\" after the cursor leaves.\n const isHoveredEdge = activeEdgeKey === link.key;\n const fromGroup = groupKeys[link.from] ?? link.from;\n const toGroup = groupKeys[link.to] ?? link.to;\n const bothInHoveredGroup = !!activeGroup && fromGroup === activeGroup && toGroup === activeGroup;\n // R63: also gate \"no filter\" on activeGroup so pinnedGroup\n // alone activates the in-group 1.3× boost + non-group dim.\n // When neither a node nor a group is in focus, mul is 1.\n // R77: when the user hovers the \"N active links\" chip, the\n // baseline 1× becomes 1.5× — every flow brightens at once.\n // Sits inside the no-other-hover branch so it doesn't fight\n // the edge-hover (2.0×) or node-hover (1.7×) priorities.\n const edgeOpacityMul = isHoveredEdge\n ? 2.0\n : activeEdgeKey\n ? 0.35\n : !hoveredAlias && !activeGroup\n ? (hoveredActiveLinks ? 1.5 : 1)\n : (link.from === hoveredAlias || link.to === hoveredAlias)\n ? 1.7\n : bothInHoveredGroup\n ? 1.3\n : 0.35;\n // Round 50: the hovered edge also visibly thickens so the eye\n // tracks it even at low message counts (width was 3 → 4.5 on\n // hover). 1.4× is enough to read as \"lifted\" without\n // breaching the 16-px hitbox bound.\n const renderWidth = isHoveredEdge ? Math.min(width * 1.4, 10) : width;\n return (\n <g\n key={link.key}\n // Round 172 / Loop: edges fade-in alongside the R9\n // staggered node reveal so the canvas first-paint\n // reads as one coordinated wave instead of \"edges\n // pop, nodes ease in\". The .anet-fade-in CSS\n // animation (0.15s ease-out, runs once on mount,\n // R3 origin) plays only on first render — React\n // preserves the <g> via the link.key on subsequent\n // re-renders so the animation doesn't replay when\n // flowLinks recomputes (every 5s SSE poll).\n // Animation-delay offsets each edge by 280ms +\n // 35ms × index so edges start fading in AFTER\n // most nodes have appeared (node R9 stagger caps\n // at ~600ms; ring layout R72 emanates 0→540ms).\n // Cap at 20 indices so a busy fleet with 50\n // flowLinks still finishes within ~1s. Respects\n // prefers-reduced-motion via R29 globals.css\n // blanket that neutralises animation-duration.\n className=\"anet-fade-in\"\n style={{\n animationDelay: `${280 + Math.min(index, 20) * 35}ms`,\n }}\n data-edge-group={link.key}\n >\n {/* Round 48 / Loop: invisible hover hitbox — visible flow\n path is 3-7 px wide and damn hard to hover precisely.\n Stack a transparent 16-px-wide stroke behind it so the\n cursor only needs to be ~8 px from the line for the\n tooltip to fire. Native <title> moves here; the\n visible path no longer needs it. pointer-events on\n the visible path drop to \"none\" since the hitbox\n owns the hover surface. */}\n <path\n d={path}\n fill=\"none\"\n stroke=\"transparent\"\n strokeWidth={Math.max(width + 10, 16)}\n style={{ pointerEvents: 'stroke' }}\n data-edge-hitbox\n onMouseEnter={() => setHoveredEdgeKey(link.key)}\n onMouseLeave={() => setHoveredEdgeKey(prev => prev === link.key ? null : prev)}\n >\n <title>{tooltip}</title>\n </path>\n {/* Round 166 / Loop: stroke-width transition pairs\n with R164 edge badge r-lift. Pre-R166 the\n visible flow path's hover thickening (R50:\n renderWidth = isHoveredEdge ? width * 1.4 :\n width) snapped instantly even though opacity\n transitioned smoothly. Edge hover now lifts\n the line AND the badge in coordinated 300ms\n ease-out motion. Drop the Tailwind transition\n class for inline style so both opacity and\n stroke-width pick up the same timing without\n arbitrary-property class compilation risk.\n data-edge-visible exposes the path for test\n probes (the R48 hitbox sibling already has\n data-edge-hitbox). Respects prefers-reduced-\n motion via the R29 globals.css blanket\n override that neutralises transition-duration\n universally. */}\n {/* Round 245 / Loop: edge surface picks up stroke\n color transition for theme-toggle smoothing.\n R166 already eased opacity + stroke-width on the\n visible flow path; the stroke COLOR (pal.flowEdge:\n cyber cyan ↔ light emerald) and the underlying\n flow-rail's stroke (pal.flowPath: cyber pale-sky\n ↔ light slate-600) still snapped on theme switch.\n The rest of the topology smooths theme through R4\n transitions (status rings) / R242 chat-target ring\n / R244 halos / R241 hub spokes / R240 backdrop\n spokes — R245 closes the edge surface.\n\n Visible flow path: append 'stroke 300ms ease-out'\n to the existing transition list (300ms matches\n R166 opacity + stroke-width pace).\n\n Flow rail (dashed underline): convert the Tailwind\n `transition-opacity` className to inline style so\n we can list opacity AND stroke together at 300ms\n ease-out (same idiom R201 used on the working/\n online chips to splice in additional properties\n beside Tailwind's). data-edge-flow-rail attr\n surfaces the path for test introspection. */}\n <path\n d={path}\n fill=\"none\"\n stroke={pal.flowEdge}\n strokeWidth={renderWidth}\n opacity={Math.min(1, (isLight ? 0.22 : 0.28) * fresh * edgeOpacityMul)}\n filter={isLight ? undefined : 'url(#topo-glow)'}\n markerEnd={`url(#${arrowId})`}\n data-edge-visible={link.key}\n style={{\n pointerEvents: 'none',\n transition: 'opacity 300ms ease-out, stroke-width 300ms ease-out, stroke 300ms ease-out',\n }}\n />\n <path\n id={`flow-path-${index}`}\n d={path}\n fill=\"none\"\n stroke={pal.flowPath}\n strokeWidth=\"1\"\n strokeDasharray=\"2 12\"\n opacity={Math.min(1, (isLight ? 0.4 : 0.75) * fresh * edgeOpacityMul)}\n data-edge-flow-rail={link.key}\n style={{ transition: 'opacity 300ms ease-out, stroke 300ms ease-out' }}\n />\n {!reducedMotion && (\n /* Round 103 / Loop: phase-stagger the particles so\n concurrent edges don't pulse in lockstep. SMIL\n `begin` accepts negative offsets to shift the cycle\n backwards in time, which means the particle starts\n mid-flight on first paint — no visible \"all\n particles spawn from source simultaneously\" tell.\n `(index * 0.37) % duration` gives a deterministic,\n well-distributed offset (the golden-ratio-ish 0.37\n fraction prevents lining up when N is a small\n multiple). Edge order is stable (sorted by recent\n activity), so the offsets feel calm rather than\n reshuffling each refresh. */\n <circle\n r=\"4\"\n fill={pal.flowParticle}\n filter={isLight ? undefined : 'url(#topo-glow)'}\n opacity={Math.min(1, fresh * edgeOpacityMul)}\n data-edge-particle={link.key}\n /* Round 252 / Loop: particle picks up fill +\n opacity transition for theme-toggle smoothing.\n Pre-R252 pal.flowParticle (cyber #fef08a yellow\n ↔ light #f59e0b amber) snapped on theme toggle\n while every other edge element eased (R245\n paths, R251 badge, R242 chat-target, R233\n endpoint ring). opacity is freshness-driven so\n it transitions per-frame as fresh decays anyway\n — but adding opacity to the explicit transition\n list also covers theme toggle (R3 className\n transition-opacity duration-300 was previously\n absent on this circle). */\n style={{ transition: 'fill 200ms ease-out, opacity 200ms ease-out' }}\n >\n <animateMotion\n dur={`${duration}s`}\n begin={`-${stagger.toFixed(3)}s`}\n repeatCount=\"indefinite\"\n path={path}\n />\n </circle>\n )}\n {/* Round 75 / Loop: arrival ping at the destination. The\n particle currently fades into the arrow marker\n silently — adding a small radiating ring synchronised\n to the particle's period turns message delivery into\n a visible event. begin = -dur*0.92 offsets the\n animation so the ring expands NEAR the end of each\n cycle (≈when the particle arrives). Gated by\n reducedMotion and on fresh > 0.5 — stale edges that\n haven't fired in minutes don't need the eye-grab.\n data-arrival-ping for testability.\n\n Round 279 / Loop: arrival ping RETIRED (R75 + R228\n + R231 + R252 family) per 减法 cut #5. Per active\n edge the SMIL family was: particle (R50\n animateMotion) + arrival ping (R75 r+opacity SMIL)\n + dispatch pulse (R76 r+opacity SMIL). For a 5-\n edge fleet that's 5×3 = 15 simultaneous SMIL.\n The PARTICLE (a moving dot along the path) is the\n primary \"data flowing from A → B\" visual signal;\n ping + pulse are secondary \"arrival/dispatch\n confirmation\" that the moving particle already\n conveys. Cull ping + pulse, keep particle.\n `false &&` gates the render; code preserved for\n rollback. */}\n {false && !reducedMotion && fresh > 0.5 && (\n <circle\n cx={to.x}\n cy={to.y}\n r=\"0\"\n fill=\"none\"\n stroke={pal.flowEdge}\n strokeWidth=\"1.5\"\n opacity=\"0\"\n /* Round 252 / Loop: stroke transition for theme\n toggle. SMIL animates r + opacity continuously;\n stroke is static per render but theme-driven\n (pal.flowEdge: cyber cyan ↔ light emerald). */\n style={{ pointerEvents: 'none', transition: 'stroke 200ms ease-out' }}\n data-arrival-ping={link.key}\n >\n {/* Round 228 / Loop: pulse-pop ease curves on the\n arrival ping SMIL — extends R227's keySplines\n adoption from the click ripple (one-shot, two-\n value linear) to the canvas's repeating delivery-\n confirmation surfaces. Both <animate>s use\n calcMode=spline with keyTimes='0;0.5;1' (two\n segments).\n • r grows 0→14→22 (monotonic): unified ease-out\n across both segments — the ring decelerates\n as it expands, settling at its widest.\n • opacity bumps 0→0.55→0 (pulse): ease-out on\n the rise (fast appearance), ease-in on the\n fall (slow start of fade then accelerates) —\n the canonical \"pulse-pop\" kinetic shape.\n Together r decelerating and opacity pulse-popping\n give the arrival ping a real-event physicality\n instead of the prior linear-velocity tick. */}\n <animate\n attributeName=\"r\"\n values=\"0;14;22\"\n dur={`${duration}s`}\n begin={`-${((stagger + duration * 0.92) % duration).toFixed(3)}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.25 0.1 0.25 1;0.25 0.1 0.25 1\"\n />\n <animate\n attributeName=\"opacity\"\n values=\"0;0.55;0\"\n dur={`${duration}s`}\n begin={`-${((stagger + duration * 0.92) % duration).toFixed(3)}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.25 0.1 0.25 1;0.42 0 1 1\"\n />\n </circle>\n )}\n {/* Round 76 / Loop: source dispatch pulse — mirror to the\n R75 arrival ping. begin = 0s (start of cycle) so the\n ring expands as the particle LEAVES the source. Only\n fires for high-traffic edges (link.count >= 3) — on\n quiet conversations the canvas should stay calm; on\n busy senders the pulse plus arrival ping bookend\n every message in flight, making the topology feel\n alive. Same fresh/reducedMotion gates as R75. Slightly\n smaller radius (0→12→18 vs R75's 0→14→22) so the\n source reads as \"smaller event than arrival\" — the\n destination is the meaningful endpoint.\n\n Round 279 / Loop: dispatch pulse RETIRED with the\n arrival ping (R75) above — same 减法 rationale.\n Particle remains as the sole \"data flow\" SMIL\n signal per active edge. */}\n {false && !reducedMotion && fresh > 0.5 && link.count >= 3 && (\n <circle\n cx={from.x}\n cy={from.y}\n r=\"0\"\n fill=\"none\"\n stroke={pal.flowEdge}\n strokeWidth=\"1.5\"\n opacity=\"0\"\n /* Round 252 / Loop: stroke transition for theme\n toggle. Same idiom as arrival ping above. */\n style={{ pointerEvents: 'none', transition: 'stroke 200ms ease-out' }}\n data-dispatch-pulse={link.key}\n >\n {/* Round 228 / Loop: same pulse-pop curves as the\n arrival ping above. r ease-out + opacity\n ease-out→ease-in. The dispatch pulse is smaller\n (0→12→18 vs arrival's 0→14→22), but the kinetic\n feel should be identical — both bookend a\n single message in flight, so they should\n physically ease the same way. */}\n <animate\n attributeName=\"r\"\n values=\"0;12;18\"\n dur={`${duration}s`}\n begin={`-${stagger.toFixed(3)}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.25 0.1 0.25 1;0.25 0.1 0.25 1\"\n />\n <animate\n attributeName=\"opacity\"\n values=\"0;0.45;0\"\n dur={`${duration}s`}\n begin={`-${stagger.toFixed(3)}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.25 0.1 0.25 1;0.42 0 1 1\"\n />\n </circle>\n )}\n {/* Round 100 / Loop: midpoint count badge for high-\n traffic edges. Width already gauges intensity but\n \"5 vs 12\" is hard to read off stroke alone. Render\n a compact pill at the bezier midpoint (t=0.5 →\n midpoint + perpendicular × lift/2) showing the\n integer count. Threshold link.count >= 3 keeps\n the canvas quiet for sparse traffic. Bezier\n midpoint math: for a quadratic curve M A Q C B,\n the t=0.5 point is (A + 2C + B) / 4, which\n simplifies to (midAB) + 0.5 × (control - midAB)\n = midAB + perpendicular × lift/2. */}\n {(() => {\n // Round 215 / Loop: badge always-mounts; visibility\n // crossfades via the wrapper <g>'s opacity instead of\n // React conditional mount/unmount on link.count >= 3.\n // Pre-R215 a flow's first 2 → 3 message crossing made\n // the badge appear in one frame, and a hot flow\n // tapering 3 → 2 vanished in one frame. R215 extends\n // the always-mount-opacity-gate idiom (R181 / R182 /\n // R183 ring family + R213 hub digit/highlight + R214\n // pulse dot) to the canvas-edge surface. Inner R164\n // r-lift / R188 stroke transitions continue to ease\n // their respective state flips independently. pointer\n // events gated to 'none' when invisible so a sub-\n // threshold badge can't intercept the hitbox click.\n const visible = link.count >= 3;\n const midX = (from.x + to.x) / 2;\n const midY = (from.y + to.y) / 2;\n const dx = to.x - from.x;\n const dy = to.y - from.y;\n const len = Math.hypot(dx, dy) || 1;\n const badgeX = midX + (-dy / len) * lift * 0.5;\n const badgeY = midY + ( dx / len) * lift * 0.5;\n const badgeOpacity = visible ? Math.min(1, fresh * edgeOpacityMul) : 0;\n /* R121: the badge becomes a canvas-side click-to-pin\n affordance. R100 introduced it as a passive count\n display; R116 added pinnedEdgeKey. Joining them\n lets users pin a flow directly from the canvas\n without crossing to the recent-signal panel.\n pointerEvents move from 'none' to 'all' on the\n wrapper <g>; the underlying R48 edge hitbox is\n wider (16 px) than the 18-px badge diameter so\n clicks landing on either still route correctly\n — the badge consumes its small footprint, the\n hitbox owns the rest. stopPropagation on\n pointerdown so the SVG pan capture doesn't\n redirect the click. */\n const isPinned = pinnedEdgeKey === link.key;\n // R126: hot-edge accent. The edge stroke width (line\n // 2344) already scales with count but clamps at 7 —\n // so count=5 and count=50 look identical at the line,\n // and the only signal differentiating them is the\n // integer in the badge. Bucket count into a \"hot\"\n // band (≥ 10) and flip the badge stroke to a warmer\n // tone + thicker ring so busy lanes telegraph at a\n // glance without reading the digit. Reuses the amber\n // family from R125 (chip empty-state) — semantic is\n // \"draw the eye here\"; R125 amber is in the chip row\n // above the SVG, R126 amber is on the canvas, so the\n // surfaces never compete in the same eye-sweep. Pin\n // still wins (uses legendHeadline) so a pinned hot\n // edge reads as \"locked + busy\" via the badge text\n // and edge brightness, not via a third stroke colour.\n const isHot = link.count >= 10;\n const hotStroke = isLight ? '#d97706' : '#fbbf24';\n return (\n <g\n data-edge-count-badge={link.key}\n data-edge-count-badge-pinned={isPinned ? 'true' : 'false'}\n data-edge-count-badge-hot={isHot ? 'true' : 'false'}\n data-edge-count-badge-visible={visible ? 'true' : 'false'}\n role={visible ? 'button' : undefined}\n tabIndex={visible ? 0 : -1}\n aria-pressed={visible ? isPinned : undefined}\n aria-hidden={visible ? undefined : true}\n className=\"anet-topo-svg-focus\"\n style={{\n pointerEvents: visible ? 'all' : 'none',\n cursor: visible ? 'pointer' : undefined,\n transition: 'opacity 300ms ease-out',\n }}\n opacity={badgeOpacity}\n onPointerDown={(e) => e.stopPropagation()}\n // R122: badge hover propagates to hoveredEdgeKey so\n // moving the cursor onto the badge lights the\n // same endpoint rings + edge brighten as hovering\n // the line. R121 only wired click; the badge sat\n // visually separate from the line on hover,\n // which felt like two surfaces rather than one.\n onMouseEnter={() => setHoveredEdgeKey(link.key)}\n onMouseLeave={() => setHoveredEdgeKey(prev => prev === link.key ? null : prev)}\n // Round 185 / Loop: edge badge click fires the same\n // one-shot expanding-ring ripple R14 uses for node\n // click and R52 uses for hub click — anchored at\n // the badge midpoint with the edge's own flowEdge\n // colour. Closes the click-feel idiom across all\n // three pinnable canvas surfaces (hub / node /\n // edge badge). Reused setClickRipple state machine\n // so only one ripple at a time; ts coordinate\n // guard in the setTimeout cleanup prevents an\n // older ripple from clobbering a newer one if\n // the user clicks two badges in quick succession.\n onClick={(e) => {\n e.stopPropagation();\n setPinnedEdgeKey(prev => prev === link.key ? null : link.key);\n const ts = Date.now();\n setClickRipple({ ts, x: badgeX, y: badgeY, r0: 10.5, color: pal.flowEdge });\n setTimeout(() => setClickRipple(prev => prev && prev.ts === ts ? null : prev), 600);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setPinnedEdgeKey(prev => prev === link.key ? null : link.key);\n const ts = Date.now();\n setClickRipple({ ts, x: badgeX, y: badgeY, r0: 10.5, color: pal.flowEdge });\n setTimeout(() => setClickRipple(prev => prev && prev.ts === ts ? null : prev), 600);\n }\n }}\n >\n <title>{isPinned\n ? `${link.from} → ${link.to} (${link.count}) — click to release pin`\n : `${link.from} → ${link.to} (${link.count}) — click to pin`}</title>\n {/* Round 164 / Loop: edge badge gains hover-lift\n to match the 5-surface hover-elevation\n family (R51 node / R135 panel / R142 group\n box / R143 recent row / R144 legend row).\n Pre-R164 the badge had R122 hover→edge-brighten\n propagation but the badge ITSELF stayed\n static, so the cursor-on-target feedback\n felt mismatched with every other interactive\n surface. Bumping r 9 → 10.5 on hover OR pin\n gives the same \"lift\" gesture in canvas\n space (the badge sits on a curved path, so\n translate-Y wouldn't track the line; radius\n growth is the SVG-native equivalent). Pin\n and hover share the lift so a pinned badge\n stays visually raised even after mouseleave —\n mirrors R143/R144 where row pin gets the\n same lift as row hover. Pinned still keeps\n its R121 stroke change (legendHeadline +\n width 2) so pin and hover stay\n discriminable on the same lifted state.\n strokeWidth stays at 1 / 2 — won't trip the\n R51 overlap-test sentinels (1.5 / 3 are\n reserved). transition keeps the lift smooth\n (180ms ease-out) and respects prefers-\n reduced-motion via the globals.css blanket\n override that neutralises transitions.\n\n Six surfaces now share the hover-elevation\n idiom: nodes (R51), panels (R135), group\n boxes (R142), recent rows (R143), legend\n rows (R144), edge badges (R164). */}\n {/* Round 188 / Loop: extend the badge transition to\n include stroke + stroke-width. R164 added the\n r 9↔10.5 lift; R188 closes the smoothness gap\n on the R121 pin-stroke flip (cyan flowEdge ↔\n legendHeadline) and R126 hot-lane flip (cyan\n ↔ amber). Both used to snap when crossing the\n state boundary (pin click, or count crossing\n 10). Now they ease 300ms through the colour\n and width change — same idiom R167 uses for\n the node status ring. The badge strokeWidth\n values are 1/2 (not R51 sentinels 1.5/3) so\n the always-rendered badge stays invisible to\n the overlap-test guard rails. */}\n {/* Round 251 / Loop: edge badge circle transition\n list grows fill + opacity at 200ms so theme\n toggle no longer snaps the badge background\n while the rest of the circle eases.\n Pre-R251:\n r 180ms (R164 hover lift)\n stroke 300ms (R188 hot/pinned colour flip)\n stroke-width 300ms (R188 hot/pinned width flip)\n fill (pal.legendBox.fill: cyber #020617 ↔ light\n #ffffff) and opacity (cyber 0.82 ↔ light 0.95)\n were theme-driven but missed from the list —\n the badge chrome snapped on theme switch while\n the per-edge ring + visible flow path (R245)\n and per-node surfaces (R246) all eased.\n R251 closes the per-edge surface theme-toggle\n smoothness — every theme-driven property on\n every edge element now eases under cyber↔light. */}\n <circle\n cx={badgeX} cy={badgeY}\n r={isHoveredEdge || isPinned ? 10.5 : 9}\n fill={pal.legendBox.fill}\n stroke={isPinned ? pal.legendHeadline : isHot ? hotStroke : pal.flowEdge}\n strokeWidth={isPinned ? 2 : isHot ? 2 : 1}\n opacity={isLight ? 0.95 : 0.82}\n data-edge-badge-lifted={(isHoveredEdge || isPinned) ? 'true' : 'false'}\n style={{ transition: 'r 180ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out, fill 200ms ease-out, opacity 200ms ease-out' }}\n />\n {/* Round 224 / Loop: edge badge text gains the 4th\n pin-signature typography. Pre-R224 the digit\n rendered with no transition surface: when the\n flow crossed count=10 (isHot flip) the badge\n stroke eased 300ms (R188) but the digit itself\n stayed dead-typographic. R224 adds two clean\n improvements stacked on the same <text> node:\n\n 1) fontVariantNumeric: 'tabular-nums' — locks\n digit width so a 9→10 transition doesn't\n jitter the textAnchor='middle' centering by\n half a glyph. The badge is on a curved\n flow path; any width-change of the centered\n digit visibly shifts the anchor relative\n to the underlying circle. Info-density\n win — digits transition cleanly without\n pixel-jitter at the boundary.\n\n 2) letterSpacing pin signature, 4th surface\n after R218 group label / R219 legend row /\n R220 recent row. Baseline 0px; widens to\n 0.4px when isPinned || isHot. The transition\n marks the \"this lane just went special\"\n event typographically — same 300ms cadence\n as the R188 stroke flip, so the badge stroke\n + text co-ease on the hot/pin threshold.\n '0px' resolves to keyword 'normal' in\n computed style (R218 test trap learned);\n test parsers must accept either form.\n\n data-edge-badge-text-pin attr surfaces the\n isPinned||isHot state for introspection. */}\n <text\n x={badgeX} y={badgeY + 3}\n textAnchor=\"middle\"\n fill={pal.legendHeadline}\n fontSize=\"10\"\n fontFamily=\"monospace\"\n fontWeight=\"700\"\n data-edge-badge-text={link.key}\n data-edge-badge-text-pin={(isPinned || isHot) ? 'true' : 'false'}\n style={{\n pointerEvents: 'none',\n fontVariantNumeric: 'tabular-nums',\n letterSpacing: (isPinned || isHot) ? '0.4px' : '0px',\n transition: 'letter-spacing 300ms ease-out',\n }}\n >{link.count}</text>\n </g>\n );\n })()}\n </g>\n );\n })}\n\n {/* center hub — round 39 sized + round 13 restraint.\n The hub is the control-plane anchor. r39 gave it two outward\n pulses (r 10→38, 2.4s, double-phase) which read as \"loudest\n node in the room\" — wrong semantics: anchors don't emit,\n they hold. r13 swaps the kinetic pulse for a slow opacity\n breath on the static halo (4s, ±15% from base), so the hub\n stays alive without throwing kinetic energy outward. The\n dual-circle \"lit lamp\" core is unchanged. */}\n {layout === 'ring' && (<g\n data-topo-hub\n data-topo-hub-hovered={hoveredHub ? 'true' : 'false'}\n // Round 159 / Loop: the hub is the most visually\n // prominent interactive element on the canvas (R39\n // enlarged it, R52 made it click to fitView, R115\n // added a hover ring, R43 gave it a tooltip) — but\n // R151-R157's a11y sweep skipped it. role/tabIndex/\n // aria-label/onKeyDown bring it to parity with node <g>\n // (R151), group label hit (R152), and minimap (R157).\n // anet-topo-svg-focus picks up R156's explicit cyan\n // focus ring (default browser SVG focus rect is hard\n // to see against the dark canvas).\n role=\"button\"\n tabIndex={0}\n aria-label={(() => {\n const parts = ['Network hub'];\n if (onlineNodes.length > 0) parts.push(`${onlineNodes.length} online`);\n if (workingCount > 0) parts.push(`${workingCount} working`);\n if (flowLinks.length > 0) parts.push(`${flowLinks.length} active link${flowLinks.length === 1 ? '' : 's'}`);\n return parts.join(' · ') + ' — Enter to fit view';\n })()}\n // Round 176 / Loop: hub joins the first-paint fade-in\n // family as the 6th surface. The hub is the visual anchor\n // — every other ring-layout reveal layer (R174 tier rings,\n // R9 nodes, R172 edges) emanates outward FROM it — yet\n // pre-R176 the hub itself popped in instantly while the\n // wave it should be leading staggered around it. Adding\n // .anet-fade-in at delay 0 (no animation-delay needed)\n // places the hub as the canvas-center anchor that the\n // tier wave grows from. Composes cleanly with the existing\n // anet-topo-svg-focus class (R159 keyboard focus ring).\n className=\"anet-topo-svg-focus anet-fade-in\"\n data-topo-hub-fade-delay={0}\n style={{ cursor: 'pointer' }}\n // Stop pointerdown from reaching the SVG pan handler — same\n // reason as the node <g>: a captured pointer makes Chromium\n // fire the follow-up click on the SVG, not this group.\n onPointerDown={(e) => e.stopPropagation()}\n onMouseEnter={() => setHoveredHub(true)}\n onMouseLeave={() => setHoveredHub(false)}\n // Round 52 / Loop: hub is the visual anchor but had no click\n // action — users discovered fit-to-content only via the `f`\n // key or the chrome button. Wire the hub to fitView() so the\n // most prominent element in the canvas is also the \"re-\n // center\" affordance. A click-ripple keyed by ts confirms\n // the gesture; the hub <title> now ends with a hint.\n onClick={() => {\n fitView();\n setClickRipple({ ts: Date.now(), x: cx, y: cy, r0: 18, color: isLight ? '#059669' : '#10b981' });\n setTimeout(() => setClickRipple(prev => prev && prev.x === cx && prev.y === cy ? null : prev), 600);\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n fitView();\n setClickRipple({ ts: Date.now(), x: cx, y: cy, r0: 18, color: isLight ? '#059669' : '#10b981' });\n setTimeout(() => setClickRipple(prev => prev && prev.x === cx && prev.y === cy ? null : prev), 600);\n }\n }}\n >\n {/* Round 43 / Loop: hub `<title>` summary — hovering the\n central glow now answers \"what is this?\" with a one-line\n fleet snapshot. Duplicates the header chips by design;\n hovering the most visually prominent element is the\n most natural impulse, so satisfy it where the cursor\n already is. Falls clean: omits sub-clauses when their\n counts are zero. R52 appends a \"click to fit view\" hint.\n */}\n <title>{(() => {\n const total = sessions.length;\n const parts = [`Network hub`, `${total} session${total === 1 ? '' : 's'}`];\n if (onlineNodes.length > 0) parts.push(`${onlineNodes.length} online`);\n if (workingCount > 0) parts.push(`${workingCount} working`);\n if (flowLinks.length > 0) parts.push(`${flowLinks.length} active link${flowLinks.length === 1 ? '' : 's'}`);\n return parts.join(' · ') + '\\nclick to fit view';\n })()}</title>\n {/* grounding halo — breathes in opacity, no expansion.\n R84: breath amplitude + tempo reflect workingCount. An\n idle fleet keeps the original gentle 0.32→0.52 (light) /\n 0.08→0.16 (dark) cycle over 4s — \"heart at rest\". As\n working sessions accumulate, the peak climbs and the\n period shortens, capped at 2.4s so it never feels\n manic. Quiet fleets see zero change; busy fleets feel\n the canvas working. */}\n {(() => {\n // Bucket workingCount to keep the visual feedback discrete\n // rather than continuous: 0 / 1-2 / 3-5 / 6+. Three buckets\n // are enough — finer gradations are imperceptible.\n const busy = workingCount === 0 ? 0\n : workingCount <= 2 ? 1\n : workingCount <= 5 ? 2\n : 3;\n const peakLight = [0.52, 0.58, 0.65, 0.72][busy];\n const peakDark = [0.16, 0.20, 0.26, 0.32][busy];\n const troughLight = 0.32;\n const troughDark = 0.08;\n const dur = [4.0, 3.2, 2.7, 2.4][busy];\n const valuesLight = `${troughLight};${peakLight};${troughLight}`;\n const valuesDark = `${troughDark};${peakDark};${troughDark}`;\n return (\n <circle\n cx={cx} cy={cy} r=\"18\"\n fill={isLight ? '#d1fae5' : '#10b981'}\n opacity={isLight ? 0.42 : 0.12}\n data-hub-busyness={busy}\n /* Round 253 / Loop: hub grounding halo fill transition\n for theme toggle. Pre-R253 the base fill (cyber\n #10b981 ↔ light #d1fae5) snapped while R244's SMIL\n animate on opacity continued running. CSS fill\n transition is independent of the SMIL animate\n (different attributes), so they compose without\n conflict. */\n style={{ transition: 'fill 200ms ease-out' }}\n >\n {/* Round 244 / Loop: hub grounding halo breath gets\n ease-in-out keySplines, matching the active-node\n pulse (R243) treatment. Pre-R244 default linear\n calcMode marched opacity at constant velocity\n through the 3-value trough→peak→trough bounce —\n mechanical pacing for a 'heartbeat at rest'\n visual. R244 adds calcMode='spline' + keyTimes\n '0;0.5;1' + keySplines '0.42 0 0.58 1' ×2 (CSS\n ease-in-out on both halves), so the breath\n decelerates near the troughs AND the peak —\n lingers briefly at each extreme like a real\n heart-rest cycle. Same SMIL-easing family as\n R227 (click ripple) / R228 (edge ping+pulse) /\n R243 (active-node pulse). The breath family\n is now 2 surfaces deep at this single hub\n element — R84 amplitude/tempo bucket + R244\n curve shape. */}\n {!reducedMotion && (\n <animate\n attributeName=\"opacity\"\n values={isLight ? valuesLight : valuesDark}\n dur={`${dur}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.42 0 0.58 1;0.42 0 0.58 1\"\n />\n )}\n </circle>\n );\n })()}\n {/* core — 20px diameter, larger inner highlight reads as a \"lit lamp\"\n Round 248 / Loop: hub center core gets a fill transition.\n Pre-R248 the core circle (the visual anchor at the centre\n of the canvas, fill=isLight ? '#059669' emerald-600 :\n '#10b981' emerald-500) hard-flipped on theme toggle —\n the most visually prominent element on the canvas\n snapping while everything else (R244 halo / R241 hub\n spokes / R246 label cards / R247 side panels) eased.\n Inline transition closes the gap. data-topo-hub-core\n attr added for test introspection (the parent <g> at\n line 3587 has data-topo-hub but the core specifically\n is the canvas anchor). */}\n <circle\n cx={cx} cy={cy} r=\"10\"\n fill={isLight ? '#059669' : '#10b981'}\n data-topo-hub-core\n style={{ transition: 'fill 200ms ease-out' }}\n />\n {/* R130 / Loop: when workingCount > 0, the decorative inner\n highlight gets replaced with the workingCount digit. The\n R84 busyness breath already encodes the same metric\n through motion — adding the digit gives it a second\n visual channel right at the canvas's focal point. A\n user glancing at the hub now sees both \"the network is\n pulsing\" (motion) AND \"3 agents are working\" (digit)\n without having to scan the chip row or panels.\n\n Geometry: text at (cx, cy) with fontSize 11 monospace\n + fontWeight 700 sits inside the r=10 core (a 2-digit\n 12 reads ~12 px wide × 11 px tall, well inside the\n 20-px diameter core). Centered vertically via dy=\n \"0.36em\" — the standard SVG trick for text-vertical-\n center without measuring fontMetrics.\n\n pointerEvents:none so the digit can't intercept the\n hub click (R52 fit-to-view still fires).\n\n workingCount=0 falls through to the existing\n decorative highlight so the hub never looks empty. */}\n {/* Round 213 / Loop: hub centre crossfades the workingCount\n digit and the R130 decorative highlight when count\n crosses zero, instead of mount/unmount snap. Pre-R213 a\n fleet going from idle (workingCount=0, highlight circle)\n to first-working-node (workingCount=1, digit \"1\") swapped\n the elements in a single frame — visible flash at the\n hub's focal point. R213 uses the always-mount + opacity-\n gate pattern (R181/R182/R183 family) so both render\n concurrently and crossfade via opacity transitions.\n Geometry already overlaps (both centred on cx,cy with\n r=5 / digit-bbox ~7×11), so the dual-render adds zero\n layout cost. Reduced-motion users see a 0ms duration\n via the R29 globals.css blanket override. */}\n {/* digit (visible when workingCount > 0) */}\n <text\n x={cx} y={cy}\n textAnchor=\"middle\"\n dy=\"0.36em\"\n fill={isLight ? '#d1fae5' : '#ecfdf5'}\n fontSize=\"11\"\n fontFamily=\"monospace\"\n fontWeight=\"700\"\n opacity={workingCount > 0 ? 1 : 0}\n data-topo-hub-working-count={workingCount}\n data-topo-hub-working-count-hovered={hoveredHub ? 'true' : 'false'}\n data-topo-hub-working-count-visible={workingCount > 0 ? 'true' : 'false'}\n // Round 209 / Loop: hub workingCount digit scales 1.0 →\n // 1.08 on hub-hover, matching R177's r 14→17 ring grow.\n // Pre-R209 hovering the hub grew the ring while the\n // focal-point digit at the centre stayed planted — the\n // gesture lifted only half the structure. R209 ties the\n // digit's scale into the same hoveredHub state R177\n // already drives, so ring + digit rise as one unit.\n // transform-box: fill-box + transform-origin: center\n // anchors the scale to the digit's own bbox (same\n // idiom R184 reset-spin + R186 chrome-pop use for\n // SVG icon transforms). 200ms matches R167 node-ring\n // stroke-width interpolation pace. Reduced-motion users\n // skip the scale via the !reducedMotion gate (R29 a11y).\n // Round 225 / Loop: tabular-nums on hub digit — info-\n // density sibling to R224's edge badge tabular-nums.\n // Same physics: when workingCount crosses the 9 → 10\n // boundary the textAnchor='middle' centering jitters\n // ~3-4px because monospace fonts still have width\n // variance at the digit-vs-control boundary. Tabular\n // locks digit width so the focal point stays planted\n // through every count change. Pure visual tightening;\n // no test trap (computed font-variant-numeric resolves\n // to the keyword 'tabular-nums' verbatim).\n /* Round 253 / Loop: append fill 200ms to the hub\n digit transition list — theme toggle (cyber #ecfdf5\n ↔ light #d1fae5) was the last hub-area snap. */\n style={{\n pointerEvents: 'none',\n transform: !reducedMotion && hoveredHub ? 'scale(1.08)' : 'scale(1)',\n transformBox: 'fill-box',\n transformOrigin: 'center',\n transition: 'transform 200ms ease-out, opacity 300ms ease-out, fill 200ms ease-out',\n fontVariantNumeric: 'tabular-nums',\n }}\n >\n {workingCount}\n </text>\n {/* decorative highlight (visible when workingCount === 0) */}\n <circle\n cx={cx} cy={cy} r=\"5\"\n fill=\"#d1fae5\"\n opacity={workingCount > 0 ? 0 : 0.9}\n data-topo-hub-highlight\n data-topo-hub-highlight-visible={workingCount > 0 ? 'false' : 'true'}\n style={{\n pointerEvents: 'none',\n transition: 'opacity 300ms ease-out',\n }}\n />\n {/* R115 / Loop: hover hint ring. Stroke-only circle at r=14\n that fades in when the hub is hovered — the same idea\n R44 used for node avatars (group-hover stroke). r=14\n sits comfortably outside the r=10 core and INSIDE the\n r=18 grounding halo, so the hover indicator is fully\n contained within the existing hub footprint (no bbox\n growth, overlap test unchanged). pointerEvents:none so\n the hint can't intercept the click that produced it. */}\n {/* Round 177 / Loop: hub hover ring picks up the same\n \"lift on hover\" gesture R164 added to the edge midpoint\n badge (r=9→10.5). Pre-R177 the ring faded opacity 0→0.7\n on hover but stayed static at r=14. Adding r=14→17 on\n hover gives the gesture extra weight — the hub responds\n more confidently. Stays within the r=18 halo bbox (no\n geometry growth), so the R51 overlap-test guard rails\n still hold. strokeWidth=1.5 is the offline-node\n sentinel but the overlap-test selector is gated to\n `g[data-node]` ancestors — this hub-internal circle\n is invisible to that probe. transition list grows `r`\n alongside opacity so both ease in concert; the lift\n feels like one continuous gesture. Seventh surface in\n the hover-elevation family (R51 nodes, R135 panels,\n R142 group boxes, R143/R144 rows, R164 edge badges,\n R177 hub ring). prefers-reduced-motion respected via\n R29 globals.css blanket. */}\n <circle\n cx={cx} cy={cy}\n r={hoveredHub ? 17 : 14}\n fill=\"none\"\n stroke={isLight ? '#059669' : '#10b981'}\n strokeWidth=\"1.5\"\n opacity={hoveredHub ? (isLight ? 0.85 : 0.7) : 0}\n data-topo-hub-hover-ring\n data-topo-hub-hover-ring-radius={hoveredHub ? 17 : 14}\n /* Round 253 / Loop: hub hover ring also gets stroke\n transition for theme toggle (cyber #10b981 ↔ light\n #059669). The opacity + r transitions stay for hover\n lift; stroke closes the theme-snap. */\n style={{\n pointerEvents: 'none',\n transition: 'opacity 180ms ease-out, r 180ms ease-out, stroke 200ms ease-out',\n }}\n />\n </g>)}\n\n {/* agent nodes */}\n {[...onlineNodes, ...offlineNodes].map((session, nodeIdx) => {\n const pos = nodePositions[session.alias];\n if (!pos) return null;\n\n const sseCountFor = (session.network_id ? sseSessions[`${session.network_id}:${session.alias}`] : undefined) ?? sseSessions[session.alias];\n const isOnline = session.status !== 'offline' || !!sseCountFor;\n const status = nodeStatus(session, isOnline, isLight);\n const isActive = activeAliases.has(session.alias);\n // #113: node size scales with the S/M/L control; halos, labels,\n // badge and avatar all derive from `radius` so they follow.\n const radius = Math.round((isOnline ? 26 : 18) * nodeScale);\n // Round 109/110 (Vincent 4582 + 4583 P0): at high node counts\n // the 100px opaque label cards overlapped each other and\n // covered neighbouring avatars. But hiding text entirely went\n // too far (\"还是得有文字\"). So: below the density threshold the\n // full name+status card shows always; above it each node shows\n // a lightweight plain-text alias (no opaque card → can't\n // occlude an avatar), and the full card appears only for the\n // hovered node or once zoomed in past 1.4×.\n const showFullLabel = !denseLayout || hoveredAlias === session.alias || view.zoom >= 1.4;\n // Round 8: when a node in another group is hovered, fade this\n // one. Same-group nodes (incl. singletons matching the hovered\n // alias) stay full. Pure visual focus, geometry unchanged.\n const inFocus = !activeGroup || (groupKeys[session.alias] ?? session.alias) === activeGroup;\n // R72: in ring layout, classify the node into a tier by its\n // distance from hub centre so the first-paint stagger can\n // emanate inner → outer instead of running clockwise. Grid\n // layout doesn't have a radial structure; it keeps R9's pure\n // nodeIdx stagger. Thresholds bracket the actual tier radii\n // (single 220, dual 175/260, triple 145/215/285, offline 325+).\n let tierIdx = 0;\n if (layout === 'ring') {\n const p = nodePositions[session.alias];\n if (p) {\n const d = Math.hypot(p.x - cx, p.y - cy);\n tierIdx = d < 195 ? 0 : d < 270 ? 1 : d < 310 ? 2 : 3;\n }\n }\n\n return (\n <g\n key={session.alias}\n data-node={session.alias}\n data-tier-idx={layout === 'ring' ? tierIdx : -1}\n // R151 / Loop: node a11y compliance — match the pattern\n // R116 / R139 / R140 / R148 / R149 applied to other\n // interactive surfaces. Node <g> has been clickable for\n // chat since R45 but tab-unreachable + screen-reader-\n // unannounced. role=\"button\" + tabIndex=0 + aria-label\n // expose it; aria-pressed reflects chat-target state so\n // SR users know which node currently has the popover\n // open. onKeyDown for Enter / Space fires the same\n // setChatAlias path as onClick. preventDefault on\n // Space stops the SVG canvas from scrolling.\n role=\"button\"\n tabIndex={0}\n aria-pressed={chatAlias === session.alias}\n aria-label={`Chat with ${session.alias} (${session.status})`}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setChatAlias(session.alias);\n setClickRipple({\n ts: Date.now(),\n x: pos.x, y: pos.y, r0: radius,\n color: status.primary,\n });\n setTimeout(() => setClickRipple(prev =>\n prev && Date.now() - prev.ts >= 590 ? null : prev), 600);\n }\n }}\n // Round 3 / Loop: `anet-fade-in` runs once when the <g>\n // mounts — a new session entering the fleet (or the topology\n // first rendering) eases in instead of popping. Re-renders of\n // an existing node don't re-trigger (React preserves the <g>\n // via the alias key), so status changes don't flicker. The\n // global prefers-reduced-motion sweep already neutralises it.\n className=\"group transition-opacity anet-fade-in anet-topo-svg-focus\"\n style={{\n cursor: 'pointer',\n // Round 17 / Loop: offline nodes drop to 0.6 at rest so\n // online nodes pop without losing the offline-as-ghost\n // information. The dashed stroke + smaller radius already\n // say \"offline\"; dimming the whole group strengthens the\n // online-vs-offline hierarchy at first glance. Exempt the\n // chat-focused node — if the user explicitly opened a\n // popover targeting an offline alias, that node stays\n // full-brightness so the focus ring + popover read as one\n // selected thing rather than a dimmed selection. Group-\n // hover fade (Round 8) still wins when inFocus is false.\n // Round 49 / Loop: edge-hover endpoint highlight composes\n // OVER the inFocus/online formula — a non-endpoint node\n // dims to 0.28 (just below the inFocus 0.32 to read as a\n // stronger \"not relevant\" signal), endpoints keep their\n // base opacity. chatAlias still wins to keep the focus\n // ring legible if the user clicked through.\n // Round 55 / Loop: legend-status hover composes on the\n // SAME level as edge-hover-endpoint. Non-matching nodes\n // dim to 0.28; matching nodes stay at their base.\n // activeStatus matches: working = status==='working',\n // idle = online but not working, offline = !isOnline.\n // Round 60 / Loop: activeStatus = hoveredStatus ?? pinnedStatus\n // so the pressure-bar segment pins (R60) and the legend\n // row hover (R55) feed the same branch.\n // Round 80 / Loop: vendor-letter hover composes ABOVE the\n // activeStatus branch so hovering the vendor chip's `C`\n // dims everything except C-vendor nodes. Same dim value\n // (0.28) as edge-endpoint and status filters — visually\n // consistent. Vendor lookup uses the same vendorForModel\n // helper the avatar render uses, keyed by initial so the\n // chip and the avatar always agree on grouping.\n opacity: hoveredEdgeEndpoints && !hoveredEdgeEndpoints.has(session.alias) && chatAlias !== session.alias\n ? 0.28\n : activeVendor && chatAlias !== session.alias && (() => {\n const v = vendorForModel(session.model);\n const initial = v.id === 'unknown' ? '?' : v.initial;\n return initial !== activeVendor;\n })()\n ? 0.28\n : activeStatus && chatAlias !== session.alias && !(\n activeStatus === 'working' ? session.status === 'working'\n : activeStatus === 'idle' ? (isOnline && session.status !== 'working')\n : /* offline */ !isOnline\n )\n ? 0.28\n : !inFocus\n ? 0.32\n : chatAlias === session.alias\n ? 1\n : isOnline ? 1 : 0.6,\n // Round 9 / Loop: stagger the anet-fade-in so the topology\n // reveals as a wave on first paint instead of one big pop.\n // Cap at 24 indices (≈600ms tail) so 50-node fleets still\n // finish revealing within a beat. CSS animation-delay only\n // applies during the keyframe — re-renders without a new\n // mount (same alias key) don't replay, so status changes\n // never trigger the stagger again.\n // Round 72 / Loop: in ring layout, stagger by tier radius\n // so the topology emanates from the hub outward — inner\n // tier at 0ms, middle at ~180ms, outer at ~360ms, offline\n // at ~540ms. A small within-tier offset (nodeIdx % 6 * 25)\n // adds variety so each ring rotates in instead of all-at-\n // once popping. Grid keeps R9's pure nodeIdx stagger.\n animationDelay: layout === 'ring'\n ? `${tierIdx * 180 + (nodeIdx % 6) * 25}ms`\n : `${Math.min(nodeIdx, 24) * 25}ms`,\n // Round 51 / Loop: hover micro-lift. The label already\n // lifts on group-hover (R26). The node body — circle,\n // avatar, status ring — stays planted, so the gesture\n // reads \"label moves, body doesn't\". Now the whole <g>\n // translates -2px when hovered: avatar + ring + label\n // move as one unit. 2 px is well inside the inter-row\n // gap (cellH headroom ≥22), and CSS transform on SVG\n // <g> never affects the overlap-test geometry (the\n // test never simulates hover). useReducedMotion drops\n // the lift to 0 for prefers-reduced-motion users.\n transform: !reducedMotion && hoveredAlias === session.alias ? 'translateY(-2px)' : undefined,\n transition: 'transform 180ms cubic-bezier(0.4,0,0.2,1)',\n }}\n // Stop the pointerdown from reaching the SVG pan handler: the\n // SVG calls setPointerCapture, and a captured pointer makes\n // Chromium fire the follow-up `click` on the SVG instead of\n // this node — so without this the node's onClick never runs.\n // Side effect (intended): you pan from empty canvas, not by\n // grabbing a node.\n onPointerDown={(e) => e.stopPropagation()}\n // Track hover globally (not just under denseLayout) so the\n // Round 8 group-focus fade works at any fleet size.\n onPointerEnter={() => setHoveredAlias(session.alias)}\n onPointerLeave={() => setHoveredAlias(prev => (prev === session.alias ? null : prev))}\n onClick={() => {\n setChatAlias(session.alias);\n // Round 14 ripple — capture position, radius and status\n // colour at click time so the one-shot circle is self-\n // contained (no re-render-time recomputation needed).\n setClickRipple({\n ts: Date.now(),\n x: pos.x, y: pos.y, r0: radius,\n color: status.primary,\n });\n setTimeout(() => setClickRipple(prev =>\n prev && Date.now() - prev.ts >= 590 ? null : prev), 600);\n }}\n >\n {/* Issue #96: native hover tooltip — \"Vendor · model · Runtime\".\n Falls back to just the alias when the node reports no\n model/runtime.\n Round 33 / Loop: cwd line answers \"what is this agent on?\".\n Round 34 / Loop: for offline nodes, append \"last seen: 6m ago\"\n so the operator knows whether to wait or chase. Online nodes\n skip the line (Round 27's 1 h ghost age-out means anything\n still online has heartbeated recently — the line would just\n be noise). Accepts both ISO (\"…T06:00:28Z\") and SQL-style\n (\"… 06:00:28\" assumed UTC) formats. */}\n {(() => {\n // Round 35 / Loop: TZ-safe parsing via the shared lib helper\n // — same parseHubTime is used by isGhost above (Round 38\n // factored it to app/lib/time.ts so both paths interpret SQL\n // bare timestamps as UTC on every browser).\n const lastSeen = !isOnline && session.last_seen_at ? relativeAgo(session.last_seen_at) : null;\n // Round 98 / Loop: enrich the node tooltip with status,\n // group membership, and inbound/outbound flow counts —\n // same info-density spirit as R97 active-filter pill\n // tooltips. Hovering any node now answers \"what is this\n // and how does it sit in the topology\" without forcing\n // a click into the chat popover. Group line only shows\n // when the alias is part of a multi-member band (R106\n // prefix grouping); singletons skip it. Flow line only\n // when at least one direction has count > 0.\n const groupKey = groupKeys[session.alias];\n const groupMembers = groupKey\n ? Object.values(groupKeys).filter(k => k === groupKey).length\n : 1;\n const groupLine = groupMembers > 1 ? `group: ${groupKey} · ${groupMembers}` : null;\n // R147 / Loop: node tooltip extends R98's flow summary\n // with the actual sender / receiver aliases. R98 told you\n // \"12 in / 5 out\" but not WHO. The R97 idiom — anywhere\n // the UI shows \"N\" should hover-explain WHICH N — applied\n // to filter pills, group labels, vendor letters, pressure\n // segments, recent-row text, active-links chip; the node\n // title was the last surface still showing only the\n // aggregate. Direction-tagged: senders are who's\n // messaging THIS node (inbound), receivers are who this\n // node is messaging (outbound). 6-truncate + \"+N more\"\n // matches the pattern other tooltips use so a 30-flow\n // node doesn't paint a 30-line tooltip.\n let flowIn = 0, flowOut = 0;\n const sendersMap = new Map<string, number>(); // alias → count, inbound\n const receiversMap = new Map<string, number>(); // alias → count, outbound\n for (const fl of flowLinks) {\n if (fl.from === session.alias) {\n flowOut += fl.count;\n receiversMap.set(fl.to, (receiversMap.get(fl.to) ?? 0) + fl.count);\n }\n if (fl.to === session.alias) {\n flowIn += fl.count;\n sendersMap.set(fl.from, (sendersMap.get(fl.from) ?? 0) + fl.count);\n }\n }\n const fmtPeers = (m: Map<string, number>) => {\n const pairs = [...m.entries()].sort((a, b) => b[1] - a[1]);\n const preview = pairs.slice(0, 6).map(([a, n]) => `${a} (${n})`).join(', ');\n const suffix = pairs.length > 6 ? ` + ${pairs.length - 6} more` : '';\n return preview + suffix;\n };\n const flowLine = (flowIn + flowOut) > 0 ? `flows: ${flowIn} in / ${flowOut} out` : null;\n const sendersLine = sendersMap.size > 0 ? `← from: ${fmtPeers(sendersMap)}` : null;\n const receiversLine = receiversMap.size > 0 ? `→ to: ${fmtPeers(receiversMap)}` : null;\n return (\n <title>{[\n `${session.alias} · ${session.status}`,\n identityLine(session.model, session.runtime),\n groupLine,\n session.project_dir ? `cwd: ${session.project_dir}` : null,\n lastSeen ? `last seen: ${lastSeen}` : null,\n flowLine,\n sendersLine,\n receiversLine,\n ].filter(Boolean).join('\\n')}</title>\n );\n })()}\n {/* Round 2 / Loop: hover ring — a thin outer stroke that fades\n in when the cursor enters the node, signalling clickability\n (real-user feedback for the chat-popover open). Pure CSS via\n Tailwind group-hover, so it costs nothing per frame and\n respects prefers-reduced-motion via the global media query. */}\n <circle\n cx={pos.x}\n cy={pos.y}\n r={radius + 12}\n fill=\"none\"\n stroke={status.primary}\n // strokeWidth must NOT be 1.5 (offline status ring) or 3\n // (online status ring) — the overlap test selects by those\n // exact widths and would mis-count this invisible hover\n // ring as a node footprint.\n strokeWidth=\"2\"\n className=\"opacity-0 group-hover:opacity-70 transition-opacity duration-150\"\n style={{ pointerEvents: 'none' }}\n />\n {/* Round 11 / Loop: chat-focus ring — when the ChatPopover is\n open targeting this node, anchor a persistent ring around\n it so the floating popover visibly links back to its source\n node. Static (not pulsing) so it reads as \"selected state\"\n rather than \"this node is active\". strokeWidth=2.5 stays\n clear of the overlap-test selectors (1.5 / 3). Sits just\n outside the halo radius+8 so it never overlaps a neighbour\n (halos already pack flush in dense grids). */}\n {/* R51 chat-target ring. R120 / Loop: gentle SMIL\n breath on the ring's opacity (±0.1 over 3s) when\n chat is open + !reducedMotion. Says \"active session\n here\" continuously without animation noise — the\n ring only appears for one node at a time (the\n chatAlias), so it never competes with R84 hub\n busyness or R112 working halo for attention.\n\n Round 183 / Loop: 7th surface in the smooth-pin-\n mirror family. Pre-R183 the ring was conditionally\n mounted on `chatAlias === session.alias`; the\n className `transition-opacity duration-200` never\n fired because the element didn't exist before\n mount. Always-mounted now with opacity gated by\n isChat — the CSS transition fires cleanly on\n chat-close (smooth fade-out). The `<animate>`\n SMIL stays gated by `!reducedMotion && isChat`\n so it only runs for the active chat target; when\n chat is closed, SMIL unmounts and opacity reverts\n to attribute (0) → CSS transitions down smoothly.\n On chat-OPEN the SMIL takes over per spec, so the\n fade-in is snappier than the fade-out — acceptable\n because the user explicitly clicked the node.\n\n strokeWidth=2.5 is not a R51 sentinel value\n (sentinels are 1.5/3 inside g[data-node]), so the\n ring is invisible to the overlap-test selector\n even when always-mounted. */}\n {/* Round 242 / Loop: extend the chat-target ring's\n transition list to include stroke + filter.\n Pre-R242 only `opacity` eased (R183 200ms): a\n chat-target node going working → idle hard-\n flipped the ring's stroke colour (status.primary\n green → teal) in one frame even though the rest\n of the ring was a smooth presence. Filter (glow\n on cyber, none on light) also snapped on chat\n toggle AND on theme switch.\n\n Add `stroke 200ms ease-out` + `filter 200ms\n ease-out` so the colour and glow both ease at\n the same cadence as the opacity gate. Same\n idiom R167 (node status-ring) uses for\n coordinated colour-easing on status flip;\n R242 brings the chat-target ring up to that\n bar. */}\n {(() => {\n const isChat = chatAlias === session.alias;\n return (\n <circle\n cx={pos.x}\n cy={pos.y}\n r={radius + 14}\n fill=\"none\"\n stroke={status.primary}\n strokeWidth=\"2.5\"\n opacity={isChat ? (isLight ? 0.85 : 0.95) : 0}\n filter={!isLight && isChat ? 'url(#topo-glow)' : undefined}\n style={{ pointerEvents: 'none', transition: 'opacity 200ms ease-out, stroke 200ms ease-out, filter 200ms ease-out' }}\n data-chat-target-ring\n data-chat-target-active={isChat ? 'true' : 'false'}\n data-chat-target-breath={!reducedMotion && isChat ? 'on' : 'off'}\n >\n {!reducedMotion && isChat && (\n <animate\n attributeName=\"opacity\"\n values={isLight ? '0.72;0.95;0.72' : '0.82;1;0.82'}\n dur=\"3s\"\n repeatCount=\"indefinite\"\n />\n )}\n </circle>\n );\n })()}\n {/* Round 243 / Loop: active-node pulse (the breathing\n aura ring at r=radius+14 fill=status.primary, shown\n on nodes participating in a recent flow) gains TWO\n polishes:\n\n 1) Always-mount + opacity-gate wrapper <g>. Pre-\n R243 the circle conditionally mounted on\n isActive — when a node joined a flow, the pulse\n snap-appeared at radius+8 (first SMIL phase\n value), and when the flow stopped, snap-\n disappeared. R243 keeps the SMIL animation\n running continuously inside an opacity-gated\n <g>; isActive flips the WRAPPER opacity\n 1↔0 with a 300ms ease-out transition so the\n pulse fades in/out at its current phase\n instead of restarting from +8. The reduced-\n motion gate stays at the conditional render\n level — reduced-motion users see no pulse at\n all (no point without the animation).\n\n 12th surface in the always-mount-opacity-gate\n family (R181/R182/R183/R213×2/R214/R215/R221/\n R222/R223/R237/R243).\n\n 2) SMIL ease-in-out keySplines on both r and\n opacity animates. Pre-R243 default linear\n calcMode produced a constant-velocity breath\n (radius marched +8 → +22 at fixed dr/dt;\n opacity dimmed 0.12 → 0.02 at fixed dα/dt) —\n mechanical, not organic. calcMode='spline'\n + keyTimes='0;0.5;1' + per-segment keySplines\n '0.42 0 0.58 1' (canonical CSS ease-in-out)\n both ways gives a settled breath: slow at\n both endpoints (small and large radius / lit\n and dim opacity), fast through the middle.\n Same SMIL-easing family R227 / R228 already\n inhabits at the click ripple + edge ping +\n pulse. */}\n {!reducedMotion && (\n <g\n opacity={isActive ? 1 : 0}\n data-node-pulse={session.alias}\n data-node-pulse-active={isActive ? 'true' : 'false'}\n style={{ transition: 'opacity 300ms ease-out' }}\n >\n <circle cx={pos.x} cy={pos.y} r={radius + 14} fill={status.primary} opacity={isLight ? 0.08 : 0.12}>\n <animate\n attributeName=\"r\"\n values={`${radius + 8};${radius + 22};${radius + 8}`}\n dur=\"2.4s\"\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.42 0 0.58 1;0.42 0 0.58 1\"\n />\n <animate\n attributeName=\"opacity\"\n values={isLight ? '0.12;0.02;0.12' : '0.18;0.04;0.18'}\n dur=\"2.4s\"\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.42 0 0.58 1;0.42 0 0.58 1\"\n />\n </circle>\n </g>\n )}\n {/* Round 4 / Loop: transition-[fill,stroke,opacity] smooths\n status colour changes so idle↔working↔offline doesn't snap\n — task replies / node-rename / SSE updates ease in.\n Round 112 / Loop: working nodes get a subtle halo breath\n (±0.12 opacity at 3s cycle) so the eye can find \"what's\n busy\" at a glance without scanning chips. Idle + offline\n halos stay flat — they don't need to demand attention.\n R84 hub-center breath stays the loudest \"fleet busyness\"\n signal; this one is quieter, per-node. SMIL `<animate>`\n inside the circle, gated by reducedMotion. */}\n {/* Round 226 / Loop: working-halo breath gets per-node\n phase stagger. Pre-R226 every working node's halo\n pulsed in lockstep — all 0.73→0.92→0.73 starting at\n the same instant — which on a fleet of 4+ working\n agents reads as one mechanical metronome rather\n than an organic group of breathing entities.\n\n SMIL `<animate>` accepts negative `begin` to offset\n the cycle backwards in time (the animation starts\n mid-cycle on first paint). Using\n `(nodeIdx * 0.37) % 3` gives a deterministic,\n well-distributed offset across the 3s period —\n the same golden-ratio-ish 0.37 fraction R103 uses\n for particle phase stagger on edges. 0.37 doesn't\n line up for any small N (4 nodes → offsets 0,\n 0.37, 0.74, 1.11 — evenly spread, never\n coincident).\n\n Side benefit: when a new agent joins a busy fleet\n its halo phase is determined by its position in\n the order array, not \"when it joined\" — so a\n re-render doesn't reshuffle breath phases. Order\n is stable (R-onlineNodes sort), so the canvas\n feels calm rather than jittery on each refresh.\n\n Reduced-motion users skip the animate entirely\n (gate unchanged). Halo opacity transition on the\n parent stays for status flips. data-node-halo-\n breath-offset surfaces the chosen offset for\n test introspection. */}\n <circle\n cx={pos.x}\n cy={pos.y}\n r={radius + 8}\n fill={status.halo}\n opacity={isOnline ? (isLight ? 0.85 : 0.65) : (isLight ? 0.4 : 0.25)}\n className=\"transition-[fill,opacity] duration-300 ease-out\"\n data-node-halo-breath={!reducedMotion && session.status === 'working' ? 'on' : 'off'}\n data-node-halo-breath-offset={\n !reducedMotion && session.status === 'working'\n ? ((nodeIdx * 0.37) % 3).toFixed(3)\n : undefined\n }\n >\n {/* Round 278 / Loop: per-node working halo breath\n (R112+R226+R244 family) RETIRED per Vincent\n 5214/5215-5217 simplification ask (减法 cut #4\n after R275 chip-row, R276 orbit, R277 legend).\n\n The breath was: each working agent's halo pulses\n 0.73→0.92→0.73 (cyber 0.53→0.78→0.53) at 3 s\n cycle, R226-staggered per-node, R244-eased. For\n a 4-working fleet that's 4 simultaneous SMIL\n breaths competing with the hub-halo breath\n (R244 hub) for the \"fleet busyness\" visual\n signal.\n\n The signal is info-redundant: the hub-halo\n breath ALREADY conveys \"the network is alive\n and busy\"; per-node halo breath duplicates it\n at 4× volume. Plus working nodes are ALREADY\n distinguished by their halo color (status.halo\n green/teal/slate via R12 trio) — the static\n halo carries identity, the moving breath was\n decorative motion on top.\n\n R278 gates the SMIL animate with `false &&` so\n the code remains for rollback. Halo opacity\n stays at the BASE (non-breathing) values via\n the parent circle's `opacity` attr (0.85/0.65/\n 0.4/0.25 from R12 + isOnline gate). Working\n nodes still show green halos; they just don't\n pulse.\n\n Net: -4 SMIL animations on canvas for typical\n 4-working fleet. Combined with R276 orbit\n retirement (-4) and the hub halo breath kept\n as the SOLE \"fleet busyness\" motion signal,\n the canvas reads quieter. R226 + R244 per-node\n stagger / ease constants are dead code post-\n R278 (acceptable — family retires together). */}\n {false && !reducedMotion && session.status === 'working' && (\n <animate\n attributeName=\"opacity\"\n values={isLight ? '0.73;0.92;0.73' : '0.53;0.78;0.53'}\n dur=\"3s\"\n begin={`-${((nodeIdx * 0.37) % 3).toFixed(3)}s`}\n repeatCount=\"indefinite\"\n calcMode=\"spline\"\n keyTimes=\"0;0.5;1\"\n keySplines=\"0.42 0 0.58 1;0.42 0 0.58 1\"\n />\n )}\n </circle>\n {/* Round 111 / Loop: edge-endpoint emphasis ring. R49\n already keeps endpoint nodes at opacity 1 while\n others dim when an edge is hovered, but the\n endpoints had no POSITIVE indicator — they just\n \"stayed bright\". An accent stroke at r=radius+7\n (just inside the halo's r=radius+8 bbox so we\n don't grow the overlap footprint) clearly says\n \"these are the two participants in this flow\".\n pointerEvents:none so the node hitbox stays alive.\n\n Round 182 / Loop: the ring used to mount/unmount\n on every edge hover, snapping despite the\n opacity transition on the style. Always-mount\n with opacity gated by hoveredEdgeEndpoints — same\n pattern R181 used for the legend pin ring. The\n transition now actually fires on hover entry\n and exit. 6th surface in the smooth-pin-mirror\n family (R165/R180/R181 + this round).\n\n strokeWidth=1.6 (was 1.5) deliberately escapes\n the R51 overlap-test sentinel `circle[stroke-\n width=\"1.5\"]`: an always-mounted r=radius+7 ring\n inside g[data-node] would otherwise be picked\n before the actual status ring (r=radius) by\n querySelector document order, breaking the\n test's node-bbox read. 1.5 → 1.6 is visually\n imperceptible (6.7% thicker) but the exact-\n string CSS attribute selector no longer\n matches. */}\n {(() => {\n const isEndpoint = hoveredEdgeEndpoints && hoveredEdgeEndpoints.has(session.alias);\n /* Round 233 / Loop: endpoint ring picks up a stroke-\n width thicken on edge-hover, completing the hover-\n elevation gesture across the whole edge surface.\n Pre-R233 hovering an edge eased the visible path\n stroke (R166) and lifted the badge r (R164) — but\n the two endpoint rings only faded IN (R182\n opacity gate). Now they ALSO thicken 1.6 → 2.4 on\n hover, in 180ms ease-out matching R164 badge lift.\n The endpoint nodes feel like they \"rise to meet\"\n the edge as the cursor approaches it, instead of\n just appearing.\n\n 1.6 and 2.4 both escape the R51 overlap-test\n sentinels (1.5 / 3 are reserved) — 2.4 sits\n comfortably between, visually 50% thicker than\n baseline so the gesture reads but the radius is\n unchanged (still r=radius+7) so geometry stays\n calm and the topo-overlap-test stays green. 9th\n surface in the hover-elevation family (R51\n nodes / R135 panels / R142 group boxes / R143-\n R144 rows / R164 edge badges / R177 hub ring /\n R229 group-label count brighten / R233 endpoint\n ring stroke-width). data-edge-endpoint-ring-\n stroke-width attr surfaces the chosen value for\n test introspection. */\n return (\n <circle\n cx={pos.x}\n cy={pos.y}\n r={radius + 7}\n fill=\"none\"\n stroke={pal.flowEdge}\n strokeWidth={isEndpoint ? 2.4 : 1.6}\n opacity={isEndpoint ? (isLight ? 0.9 : 0.85) : 0}\n data-edge-endpoint-ring\n data-edge-endpoint-active={isEndpoint ? 'true' : 'false'}\n data-edge-endpoint-ring-stroke-width={isEndpoint ? 2.4 : 1.6}\n style={{ pointerEvents: 'none', transition: 'opacity 180ms ease-out, stroke-width 180ms ease-out' }}\n />\n );\n })()}\n {/* Round 167 / Loop: extend the node status-ring\n transition to include stroke-width — symmetric\n with R165 (pressure-bar width) and R166 (edge\n stroke-width). Pre-R167 only fill+stroke colors\n transitioned smoothly; stroke-width snapped from\n 3 (online) to 1.5 (offline) when a session\n transitioned. With stroke-width in the transition\n list the ring smoothly contracts as a node goes\n offline (or expands when it comes back).\n strokeDasharray stays binary (none ↔ '5 5')\n because dash values don't interpolate cleanly\n between continuous and discrete forms.\n Inline style replaces the Tailwind transition-\n [fill,stroke] className for stable arbitrary\n property compilation. Respects prefers-reduced-\n motion via R29 globals.css blanket override.\n data-node-status-ring exposes this circle for\n test probing — the overlap-test guard on\n stroke-width=\"3\"/\"1.5\" still works against the\n DOM attribute value (React-rendered, not\n interpolated). */}\n <circle\n cx={pos.x}\n cy={pos.y}\n r={radius}\n fill={isOnline ? pal.nodeFill.online : pal.nodeFill.offline}\n stroke={status.primary}\n strokeWidth={isOnline ? 3 : 1.5}\n strokeDasharray={isOnline ? 'none' : '5 5'}\n filter={isOnline && !isLight ? 'url(#topo-glow)' : undefined}\n data-node-status-ring={status.label}\n style={{\n transition: 'fill 300ms ease-out, stroke 300ms ease-out, stroke-width 300ms ease-out',\n }}\n />\n {/* v0.10.0 Hero 1+2 / §3.F server-health node-ring tint.\n When the host server this agent runs on is in the\n `red` tier (CPU/Mem/Disk worst-of ≥ 85% per\n classifyServer threshold), draw a faint amber outer\n halo at radius+8. strokeWidth=2.5 stays clear of the\n R51 overlap-test sentinels (1.5 = offline status ring,\n 3 = online status ring). pointerEvents:none so the\n halo can't intercept node clicks. Falls back silent\n when host telemetry hasn't shipped yet (hostHealthMap\n is empty until commhub returns telemetry). Composes\n with R209 hover-ring (which sits BETWEEN the avatar\n and this halo on hover — different radii). */\n }\n {(() => {\n const tier = hostHealthMap.get(session.server);\n if (tier !== 'red') return null;\n return (\n <circle\n cx={pos.x}\n cy={pos.y}\n r={radius + 8}\n fill=\"none\"\n stroke={isLight ? '#d97706' : '#fbbf24'}\n strokeWidth=\"2.5\"\n opacity=\"0.6\"\n data-node-server-health=\"red\"\n data-node-server-host={session.server}\n style={{\n pointerEvents: 'none',\n transition: 'stroke 200ms ease-out, opacity 200ms ease-out',\n }}\n />\n );\n })()}\n {/* Issue #96: node \"avatar\" is now driven by the model\n vendor. Decision order:\n 1. ?brand=intern flag, or an intern-aliased node with\n no model field → 书生 coin (preserves #79).\n 2. vendor has a packaged logo asset → that logo image\n (intern-s1-* models land here via vendorForModel).\n 3. known vendor, logo asset not shipped yet → a\n vendor-tinted monogram (spec-mandated fallback).\n 4. unknown vendor / null model → the prefix-group\n hue-hashed initial (#83/#99 behaviour, unchanged). */}\n {(() => {\n const ar = Math.round((isOnline ? 14 : 10) * nodeScale);\n const size = radius * 2;\n const vendor = vendorForModel(session.model);\n const internByAlias = /书生|书小生|intern/i.test(session.alias);\n\n if (isIntern || internByAlias || vendor.logo) {\n return (\n <image\n href={vendor.logo ?? '/intern_avatar.png'}\n x={pos.x - size / 2}\n y={pos.y - size / 2}\n width={size}\n height={size}\n preserveAspectRatio=\"xMidYMid meet\"\n />\n );\n }\n if (vendor.id !== 'unknown') {\n // Known model house, logo asset not in public/vendors/\n // yet — vendor-tinted monogram stands in.\n /* Round 283 / Loop: monogram circle strokeWidth bumps\n 1 → 1.5 per Vincent 5216 \"书生头像风格延续 — 其他\n vendor 头像 plain text/abbreviation 比书生差, polish\n 升级\". Without real vendor logo PNG/SVG assets in\n public/vendors/, the monogram is the visual stand-\n in; bumping its ring weight from 1 to 1.5 narrows\n the visual-quality gap with the 书生 image avatar\n (which is a designed PNG, naturally more\n substantial). The 1px → 1.5px stroke is the same\n weight increment R268 used on the chrome-strip\n border unification — small but perceptible. The\n prefix-group fallback (line ~5172) stays at\n strokeWidth=1 since that's for UNKNOWN vendors\n where less visual weight signals \"we don't know\n what this is\" appropriately. */\n return (\n <>\n <circle cx={pos.x} cy={pos.y} r={ar} fill={vendor.mono.bg} stroke={vendor.mono.ring} strokeWidth=\"1.5\" />\n {/* Round 284 / Loop: known-vendor monogram letter\n swaps fontFamily monospace → system sans-serif.\n Continuation of R283 Vincent 5216 \"vendor 头像\n polish 升级\". A single centered letter does not\n benefit from monospace's digit-alignment\n property — its only effect at this scale is to\n land a slightly thinner, more code-text-like\n glyph. The system stack ('-apple-system',\n 'BlinkMacSystemFont', 'Segoe UI', 'Inter',\n 'sans-serif') picks the OS-preferred designed\n sans-serif, which renders a fuller, more\n \"badge-mark\" letterform — narrowing the\n visual-quality gap with the 书生 PNG (which is\n a hand-designed image). data-monogram-letter\n exposes the element for test probing.\n\n The prefix-group fallback at line ~5197\n INTENTIONALLY stays on monospace — same\n contrast pattern R283 established for ring\n stroke: \"designed glyph\" = known vendor,\n \"code text\" = unknown vendor bucket. */}\n <text\n x={pos.x} y={pos.y} dy=\"0.34em\" textAnchor=\"middle\"\n fill={vendor.mono.text} fontSize={ar}\n fontFamily=\"-apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, sans-serif\"\n fontWeight=\"700\"\n data-monogram-letter={vendor.initial}\n >\n {vendor.initial}\n </text>\n </>\n );\n }\n // Round 106 (issue #83): hue keyed to the prefix group,\n // not the full alias — every 通信* node shares one color.\n const c = aliasAvatarColors(groupKeys[session.alias] || session.alias);\n return (\n <>\n <circle cx={pos.x} cy={pos.y} r={ar} fill={c.bg} stroke={c.ring} strokeWidth=\"1\" />\n <text\n x={pos.x}\n y={pos.y}\n dy=\"0.34em\"\n textAnchor=\"middle\"\n fill={c.text}\n fontSize={ar}\n fontFamily=\"monospace\"\n fontWeight=\"700\"\n >\n {aliasInitial(session.alias)}\n </text>\n </>\n );\n })()}\n {/* Issue #96: runtime badge — small corner glyph marking the\n execution shell (CLI / SDK / HTTP API). Sits bottom-right\n of the avatar; colours kept off the working/idle/offline\n status hues. Absent when the node reports no runtime. */}\n {(() => {\n const rt = runtimeIdentity(session.runtime);\n if (!rt) return null;\n const br = isOnline ? 7 : 5.5;\n const bx = pos.x + radius * 0.72;\n const by = pos.y + radius * 0.72;\n const icon = br * 2 * 0.62;\n // Round 208 / Loop: runtime badge joins the micro-lift\n // radius-axis family. R177 grew the hub ring on hover\n // (r 14→17); R197 grew the legend swatch (r 5.5→7);\n // R208 closes the trio at per-node grain — the runtime\n // badge (CLI/SDK/HTTP indicator at avatar bottom-right)\n // pops r 7→8 (online) / 5.5→6.5 (offline) when the\n // parent node is hovered, with stroke 1.5→2 for\n // matching emphasis. R26 already lifts the label 1.5px\n // and R194 elevates its drop-shadow; R208 gives the\n // runtime indicator its own hover acknowledgement so\n // every per-node surface participates in the gesture.\n // CSS r-as-property + stroke-width are transitionable\n // (same support matrix R197/R198/R199 leveraged:\n // Chrome ≥95 / Safari ≥16 / FF ≥70). data-runtime-\n // badge-active exposes the gate for tests.\n const isNodeActive = !reducedMotion && hoveredAlias === session.alias;\n return (\n <g style={{ pointerEvents: 'none' }}>\n <circle\n cx={bx} cy={by} r={br}\n fill={pal.containerBg}\n stroke={rt.color}\n strokeWidth=\"1.5\"\n data-runtime-badge={session.alias}\n data-runtime-badge-active={isNodeActive ? 'true' : 'false'}\n style={{\n r: isNodeActive ? `${br + 1}px` : `${br}px`,\n strokeWidth: isNodeActive ? '2px' : '1.5px',\n transition: 'r 150ms ease-out, stroke-width 150ms ease-out',\n } as React.CSSProperties}\n />\n <g transform={`translate(${bx - icon / 2} ${by - icon / 2}) scale(${icon / 24})`}>\n <path d={rt.iconPath} fill=\"none\" stroke={rt.color} strokeWidth=\"2.4\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </g>\n </g>\n );\n })()}\n {/* Round 294 / Loop: per-node \"working\" pulse dot retired.\n The pulse was R24's per-node working indicator — a\n small green circle at the top of each working node,\n SMIL-animated opacity 1→0.25→1. After R278 retired the\n working halo, R279 retired arrival ping + dispatch\n pulse, R280 retired backdrop spokes, the pulse dot\n was the last surviving per-node SMIL animation in\n the original \"working = breathing\" visual family.\n Status info is preserved through 4 redundant signals:\n status ring green color (R167), label sub-text\n 'working' (R211), chip-row 'X working' count (top\n of canvas), hub centre digit (R130). With 30+\n working nodes on a real fleet, 30 simultaneous SMIL\n pulses add cognitive load with zero new information.\n Same R275-R281/R290/R291 减法 family idiom — the\n last 'wiggling per-node decoration' retires. Gated\n via `{false && ...}` per the R276/R278/R279/R280\n rollback-friendly pattern; the block stays in the\n file documented + dead-coded so future readers see\n the retired pulse-dot rationale + can A/B-restore\n it by flipping the gate. */}\n {false && (() => {\n const sse = sseCountFor ?? 0;\n const dur = sse >= 4 ? '0.7s' : sse >= 2 ? '0.9s' : '1.2s';\n const visible = session.status === 'working';\n return (\n <g\n data-pulse-wrapper={session.alias}\n data-pulse-visible={visible ? 'true' : 'false'}\n style={{\n opacity: visible ? 1 : 0,\n transition: 'opacity 300ms ease-out',\n pointerEvents: 'none',\n }}\n >\n <circle cx={pos.x} cy={pos.y - (radius - 6)} r=\"2.5\" fill={pal.flowParticle} data-pulse-dur={dur} opacity={reducedMotion ? 0.6 : undefined}>\n {!reducedMotion && (\n <animate attributeName=\"opacity\" values=\"1;0.25;1\" dur={dur} repeatCount=\"indefinite\" />\n )}\n </circle>\n </g>\n );\n })()}\n\n {/* Round 98 (issue #61): label rect 124px → 100px.\n Round 109/110 (Vincent P0): full opaque card below the\n density threshold / on hover / when zoomed; otherwise a\n lightweight plain-text alias that keeps every node\n labelled without an opaque box covering its neighbours.\n Round 15 / Loop: when nodeScale=S the node shrinks 30%\n but the label card was staying full-size, so the label\n visually outweighed the small node. Tighten the card\n frame, alias / sub fontSize, drop-offset and truncate\n length specifically for S; M and L keep their existing\n sizes (M ≈ L for labels by design — the S user is the\n one who explicitly asked for a denser view). */}\n {(() => {\n const isSmall = nodeScale < 0.8;\n const cardW = isSmall ? 88 : 100;\n const cardH = isSmall ? 36 : 42;\n const cardTopY = isSmall ? -12 : -14;\n const aliasFs = isSmall ? 11 : 12;\n const subFs = isSmall ? 8 : 9;\n const subY = isSmall ? 15 : 17;\n const dropY = isSmall ? 18 : 22;\n const fullMax = isSmall ? 11 : 12;\n const denseFs = isSmall ? 9 : 10;\n const denseDrop = isSmall ? 12 : 14;\n // Round 26 / Loop: micro-lift the label group on hover —\n // 1.5 px upward, 200 ms ease. Pairs with the Round 18\n // group-box hover-accent treatment to give the same\n // \"this is the focused element\" feedback at the per-\n // node level. CSS transform stacks onto the SVG\n // positioning transform attribute (SVG 2 cascade);\n // bbox is unchanged at rest, so the overlap-test gate\n // continues to see the geometric layout it expects.\n return showFullLabel ? (\n <g transform={`translate(${pos.x}, ${pos.y + radius + dropY})`} style={{ pointerEvents: 'none' }}\n className=\"transition-transform duration-200 group-hover:-translate-y-[1.5px]\">\n {/* Round 194 / Loop: label card picks up a subtle\n drop-shadow that intensifies when the node is\n hovered — pairs the existing R26 1.5px lift\n with physical weight. Pre-R194 the card lifted\n 1.5 px on hover but had no shadow follow, so\n the gesture read as \"card translated\" rather\n than \"card rose off the canvas\". Adding a\n baseline shadow at rest (1px/2px blur) plus a\n deeper hover state (3-4px/8-12px blur) makes\n the elevation feel earned.\n Same R57/R135 hover-elevation idiom that\n panels already use, now extended to per-node\n label cards. data-node-label-card-elevation\n ('idle'/'hover') exposes the state for tests.\n Filter is theme-aware (light: slate alpha;\n dark: black alpha) so the shadow stays visible\n against both surfaces. transition: filter\n 220ms ease-out matches R135's panel-elevation\n duration so a hovered node + a hovered panel\n fade at the same rhythm. Reduced-motion users\n collapse to the rest-state shadow only — no\n hover differentiation. Per-node filter is\n gated to showFullLabel which itself is gated\n to non-dense fleets (≤16 nodes) or hovered/\n zoomed-in branches, so the cost stays bounded\n (~20-30 cards max). */}\n {/* Round 217 / Loop: label card stroke tints to\n legendAccent (cyan) on parent-node hover,\n adding a 3rd hover-feedback channel alongside\n R26 1.5px lift + R194 drop-shadow elevation.\n The card now responds at three layers when its\n parent node is hovered: lift (motion) → shadow\n (depth) → stroke (color tint). All three are\n gated by the same hoveredAlias === session\n .alias state so they ease in unison. transition\n list extends `stroke 220ms ease-out` alongside\n R194's existing filter 220ms — single pair of\n eyes on the card reads \"this is the focused\n element\" via three independent channels. Pin\n + chat states don't compete: pinning a node\n opens chat (R136) but doesn't drive hovered\n Alias, so this stroke tint is exclusively a\n pointer-on-target signal. */}\n {/* Round 246 / Loop: label card chrome picks up\n fill + opacity in its transition list. R142\n already eased filter (drop-shadow) + stroke\n (R217 cyan tint on hover); the rect's fill\n (pal.labelBox.fill: cyber #020617 ↔ light\n #ffffff) and theme-derived opacity (0.94\n cyber / 1 light) still snapped on theme\n toggle. R211 already closed the alias/sub\n text-fill snap on the same card; R246\n closes the chrome-fill snap on the rect\n BEHIND that text, so the whole card\n (background + text) transitions as one\n unit through every theme switch. Same\n 220ms cadence the existing filter/stroke\n pair uses — coordinated 4-property easing\n across the card. */}\n <rect\n x={-cardW / 2} y={cardTopY} width={cardW} height={cardH} rx=\"6\"\n fill={pal.labelBox.fill}\n stroke={!reducedMotion && hoveredAlias === session.alias\n ? pal.legendAccent\n : pal.labelBox.stroke}\n opacity={isLight ? 1 : 0.94}\n data-node-label-card={session.alias}\n data-node-label-card-elevation={\n !reducedMotion && hoveredAlias === session.alias ? 'hover' : 'idle'\n }\n style={{\n filter: !reducedMotion && hoveredAlias === session.alias\n ? (isLight\n ? 'drop-shadow(0 3px 8px rgba(15,23,42,0.20))'\n : 'drop-shadow(0 4px 12px rgba(0,0,0,0.60))')\n : (isLight\n ? 'drop-shadow(0 1px 2px rgba(15,23,42,0.08))'\n : 'drop-shadow(0 1px 2px rgba(0,0,0,0.30))'),\n transition: 'filter 220ms ease-out, stroke 220ms ease-out, fill 220ms ease-out, opacity 220ms ease-out',\n }}\n />\n {/* Round 211 / Loop: alias + sub text fill eases on\n status flip, matching R167 status-ring fill 300ms.\n Pre-R211 a node going working → idle → offline made\n the ring smoothly recolor (R167) while the label\n card's text snap-cut to the new tier hue in a\n single frame — the node \"transitioned its ring,\n flipped its text\". 300ms inline transition syncs\n all four label-card fills (alias, sub, ring fill,\n ring stroke) to the same beat so the node reads\n as one coordinated status change.\n data-node-alias-text exposes the gate for tests. */}\n {/* Round 305 / Loop: node alias label text picks\n up the pin-signature letter-spacing family\n (R219 / R220) when the node is the chat\n target. The alias is the per-node identity\n label inside the label card; when chat is\n open targeting this node, R242 already adds\n a cyan-tint stroke to the card. R305 brings\n the alias text into the same pin-signature\n family — letter-spacing 0px → 0.5px when\n chatAlias === session.alias. Same vocabulary\n R219 established for recent-row text (line\n ~6354), legend-row text (~6881), and the\n R220 edge-badge text (~4327, with 0.4 for\n hot/pin). Now the per-node alias has its\n own pin signature when chat is open on it.\n transition list extends 'letter-spacing\n 200ms ease-out' so it eases alongside the\n existing 300ms fill transition. */}\n <text\n x=\"0\" y=\"1\" textAnchor=\"middle\"\n fill={status.text}\n fontSize={aliasFs} fontFamily=\"monospace\" fontWeight=\"700\"\n data-node-alias-text={session.alias}\n data-node-alias-chat-target={chatAlias === session.alias ? 'true' : 'false'}\n style={{\n transition: 'fill 300ms ease-out, letter-spacing 200ms ease-out',\n letterSpacing: chatAlias === session.alias ? '0.5px' : '0px',\n }}\n >\n {truncate(session.alias, fullMax)}\n </text>\n <text\n x=\"0\" y={subY} textAnchor=\"middle\"\n fill={status.primary}\n fontSize={subFs} fontFamily=\"monospace\"\n data-node-sub-text={session.alias}\n style={{ transition: 'fill 300ms ease-out' }}\n >\n {status.label}{isOnline && sseCountFor != null ? ` sse:${sseCountFor}` : ''}\n </text>\n </g>\n ) : (\n // Round 212 / Loop: dense plain-text alias gets fill\n // transition on status flip — extension of R211. Pre-\n // R212 the dense fallback (denseLayout > 16 nodes,\n // where label cards collapse to plain text + R110\n // stroke halo) snap-cut its fill on tier change while\n // the status ring smoothly transitioned (R167) — the\n // card-mode equivalent that R211 just fixed at the\n // ≤16-node grain. Inline transition list combines\n // R26 transform 200ms (group-hover lift) + R212 fill\n // 300ms (status flip ease) — Tailwind transition-\n // transform on className would be displaced by inline\n // transition, so the transform property is explicit\n // in the inline list too. The group-hover:-translate-\n // y-[1.5px] className still fires the transform via\n // CSS pseudo-class; only the transition-property\n // moves to inline. Big fleets benefit most — this is\n // the path users see when their dashboard is busiest.\n <text\n x={pos.x}\n y={pos.y + radius + denseDrop}\n textAnchor=\"middle\"\n fill={status.text}\n fontSize={denseFs}\n fontFamily=\"monospace\"\n fontWeight=\"700\"\n opacity={0.9}\n className=\"group-hover:-translate-y-[1.5px]\"\n data-node-dense-alias-text={session.alias}\n style={{\n pointerEvents: 'none',\n paintOrder: 'stroke',\n transition: 'transform 200ms ease-out, fill 300ms ease-out',\n }}\n stroke={pal.containerBg}\n strokeWidth=\"3\"\n >\n {truncate(session.alias, isSmall ? 9 : 10)}\n </text>\n );\n })()}\n {/* v0.10.0 Hero 3 Wave 1 §3.E — hover detail card.\n Renders an extended-info SVG card next to the\n hovered node showing vendor / model / runtime /\n server fields that don't fit in the compact label\n card. Position flips to the left when the node is\n in the right half of the canvas so the card\n doesn't extend past the viewBox right edge. Only\n one card is visible at any time (gated on\n hoveredAlias === session.alias), so layout cost\n stays bounded.\n Reuses pal.labelBox.fill / pal.legendAccent for\n chrome consistency with the existing label card +\n legend panel. data-topo-hover-detail attribute\n exposes the element for test probes.\n Not rendered in dense layout (>16 nodes) — same\n gate as showFullLabel; dense fleets already have\n too much per-node chrome competing. */}\n {!reducedMotion && hoveredAlias === session.alias && !denseLayout && (() => {\n const v = vendorForModel(session.model);\n const rt = runtimeIdentity(session.runtime);\n const flipLeft = pos.x > VIEWBOX_W * 0.65;\n const detailW = 192;\n const detailH = 88;\n const detailX = flipLeft ? pos.x - radius - 18 - detailW : pos.x + radius + 18;\n const detailY = pos.y - detailH / 2;\n return (\n <g transform={`translate(${detailX}, ${detailY})`} data-topo-hover-detail={session.alias} style={{ pointerEvents: 'none' }}>\n <rect\n x=\"0\" y=\"0\" width={detailW} height={detailH} rx=\"8\"\n fill={pal.labelBox.fill}\n stroke={pal.legendAccent}\n opacity={isLight ? 0.98 : 0.94}\n style={{ filter: isLight ? 'drop-shadow(0 4px 12px rgba(15,23,42,0.16))' : 'drop-shadow(0 4px 12px rgba(0,0,0,0.6))' }}\n />\n <text x=\"10\" y=\"16\" fontSize=\"9\" fontFamily=\"monospace\" fill={pal.legendAccent} fontWeight=\"700\">\n {v.id !== 'unknown' ? v.label : '—'}\n </text>\n <text x=\"10\" y=\"32\" fontSize=\"10\" fontFamily=\"monospace\" fill={pal.legendHeadline}>\n {session.model || 'model · pending'}\n </text>\n <text x=\"10\" y=\"48\" fontSize=\"9\" fontFamily=\"monospace\" fill={pal.legendText}>\n {rt ? rt.label : 'runtime · pending'}\n </text>\n <text x=\"10\" y=\"64\" fontSize=\"9\" fontFamily=\"monospace\" fill={pal.legendText}>\n host · {session.server || 'unknown'}\n </text>\n <text x=\"10\" y=\"80\" fontSize=\"9\" fontFamily=\"monospace\" fill={pal.legendText} opacity=\"0.7\">\n {session.task ? truncate(session.task, 28) : 'no recent task'}\n </text>\n </g>\n );\n })()}\n </g>\n );\n })}\n\n {/* Round 14 / Loop: click ripple — one-shot expanding ring from\n the clicked node, ~500ms, status-coloured. Sits inside the\n zoom/pan <g> so it scales / pans with the topology. Keyed by\n ts so a re-click on any node (same or different) remounts the\n <circle> and the SMIL <animate> elements replay from t=0.\n strokeWidth=2 doesn't match the overlap-test selectors. */}\n {/* Round 227 / Loop: click-ripple SMIL gets ease-out curve.\n Pre-R227 both <animate>s ran with default calcMode=linear,\n which made the ripple grow at constant velocity from\n r0+4 → r0+30 over 500ms — a mechanical \"expansion at\n uniform rate\" feel that didn't match the rest of the\n topology's interaction vocabulary (every CSS transition\n on hover-lift, status-flip, pin-signature uses\n `ease-out`). On click, the ripple is the user's primary\n \"I did that\" confirmation feedback — it should feel\n fast-then-settle like a real pressure wave, not metric.\n\n calcMode=\"spline\" + keyTimes=\"0;1\" + keySplines=\"0.25 0.1\n 0.25 1\" maps directly onto CSS cubic-bezier(0.25, 0.1,\n 0.25, 1), the canonical ease-out curve. SMIL's keySplines\n uses the same 4 control-point convention as CSS but\n space-separated. Applied to BOTH the r and opacity\n <animate> elements so they ease in lockstep — the ring\n decelerates as it expands and fades, the two motions\n together feeling like one organic pulse.\n\n One change reaches three click surfaces — hub center\n (R52), node body (R14), edge midpoint badge (R185) — all\n reuse this single ripple element via the shared\n setClickRipple state. data-click-ripple attr surfaces\n the element for test introspection; calcMode attribute\n on the <animate> reflects the ease-out adoption. */}\n {clickRipple && !reducedMotion && (\n <circle\n key={clickRipple.ts}\n cx={clickRipple.x}\n cy={clickRipple.y}\n r={clickRipple.r0 + 4}\n fill=\"none\"\n stroke={clickRipple.color}\n strokeWidth=\"2\"\n opacity=\"0\"\n data-click-ripple\n style={{ pointerEvents: 'none' }}\n >\n <animate\n attributeName=\"r\"\n values={`${clickRipple.r0 + 4};${clickRipple.r0 + 30}`}\n dur=\"0.5s\"\n calcMode=\"spline\"\n keyTimes=\"0;1\"\n keySplines=\"0.25 0.1 0.25 1\"\n fill=\"freeze\"\n />\n <animate\n attributeName=\"opacity\"\n values=\"0.7;0\"\n dur=\"0.5s\"\n calcMode=\"spline\"\n keyTimes=\"0;1\"\n keySplines=\"0.25 0.1 0.25 1\"\n fill=\"freeze\"\n />\n </circle>\n )}\n\n </g>\n\n {/* #112: overlay panels (recent-signal + legend) render OUTSIDE the\n zoom/pan <g> so they stay fixed while the topology pans/zooms.\n They're sized + tucked into the top corners so every corner of\n each panel is >325px from the canvas centre — i.e. fully outside\n the outermost (offline) ring. No node on any ring can reach the\n corner triangles, so the panels never overlap a node, in ring\n OR grid layout (Vincent 4727 zero-overlap criterion). */}\n {/* latest flow labels */}\n {/* Round 57 / Loop: drop-shadow on the panel rects gives them\n card-like elevation, especially on light theme where the\n near-white fill on a near-white canvas read as pasted-on.\n data-topo-panel-elevation tag so the test can verify both\n panels carry the filter. The filter is on the rect, not\n the parent <g>, so it doesn't shadow the rows + text inside\n — only the panel chrome lifts.\n\n v0.10.0 Hero 3 Wave 1 / RFC §3.C (Vincent 5222 holdover):\n hide recent-signal panel when there's no flow to show.\n Pre-v0.10.0 the panel always-mounted with a \"no flow yet\n · send a message between agents\" placeholder. On a fresh\n fleet that's a full corner of chrome doing nothing —\n exactly the always-mount-stack 5222 calls out. Render the\n panel only when flowLinks actually has rows. R175 fade-in\n still applies — first flow that arrives still eases in.\n Composes with §3.I canvas-corner watermark (only shows\n when this panel is absent). */}\n {flowLinks.length > 0 && (\n <g\n transform=\"translate(16, 16)\"\n data-topo-panel=\"recent\"\n data-topo-panel-hovered={hoveredPanel === 'recent' ? 'true' : 'false'}\n // Round 175 / Loop: corner panels fade-in after the\n // R9/R72/R172/R173/R174 canvas content reveal. Pre-R175\n // recent-signal + legend panels appeared instantly in\n // the corners while nodes/edges/group boxes staggered\n // in around them — felt like 'panels are already\n // there, content shows up'. Delaying the panels to\n // ~700ms (after the first node wave finishes ~540ms\n // and edges begin filling in ~280ms) makes them drop\n // into place AFTER the canvas has revealed.\n // recent-signal panel at 700ms; legend at 800ms below\n // for a soft left-then-right cascade. .anet-fade-in\n // is the same R3 mount-once animation the other 4\n // wave layers use — fifth surface in the family.\n className=\"anet-fade-in\"\n data-topo-panel-fade-delay={700}\n style={{ animationDelay: '700ms' }}\n onMouseEnter={() => setHoveredPanel('recent')}\n onMouseLeave={() => setHoveredPanel(prev => prev === 'recent' ? null : prev)}\n >\n {/* Round 331 / Loop: recent-signal panel rect rx 8 → 10\n for proportional corner-radius rhythm after R330\n bumped the outer canvas wrapper to rounded-xl (12 px).\n Pre-R331 the panel sat at rx=8 (matching the legacy\n rounded-lg wrapper envelope); now it follows the\n wrapper one tier down:\n outer wrapper rounded-xl 12 px (R330)\n inner SVG panels rx=10 10 px (R331)\n inner detail card rx=8 8 px (codex 8f981a9)\n node label card rx=6 6 px (legacy R63)\n Geometry-safe: rx changes paint only, not bbox; the\n topo-overlap-test reads bbox geometry. Sibling change\n at the legend panel rect below (~line 6914) keeps\n the two corner panels symmetric. */}\n <rect\n x=\"0\" y=\"0\" width=\"230\" height=\"88\" rx=\"10\"\n fill={pal.legendBox.fill}\n stroke={pal.legendBox.stroke}\n // Round 348 / Loop: recent-signal panel rect opacity hover-\n // state bump — joins the panel-hover cue stack (R135 drop-\n // shadow boost + R345 title letter-spacing tween 0.3 → 0.4\n // + R266 fill theme-flip). Cyber 0.92 → 0.97, light 0.97 →\n // 1.0 on hoveredPanel === 'recent'. The panel \"solidifies\"\n // on hover — pure paint-level change, geometry-safe (bbox\n // unchanged so topo-overlap-test invariants hold). The\n // R247 transition list already includes `opacity 200ms\n // ease-out` so the value tween is automatic. Sibling\n // change at legend panel rect below (~line 7222).\n opacity={hoveredPanel === 'recent' ? (isLight ? 1 : 0.97) : (isLight ? 0.97 : 0.92)}\n style={{\n /* R135: drop-shadow intensifies on panel hover. Base\n shadow (2px / 6px blur) signals card elevation\n (R57); hovered (4px / 12px blur) tells the user\n the whole chrome is interactive territory — rows\n pin (R116), footer navigates (R133), legend rows\n pin status (R61). Reuses R18's KPI-card hover-\n elevation idiom for visual consistency. Theme-\n aware shadow colour stays the same; just the\n spread + blur grow.\n\n Round 247 / Loop: extend the transition list to\n include fill + stroke + opacity at 200ms. R135\n already eased filter (hover drop-shadow); the\n three theme-driven properties (pal.legendBox.fill\n cyber #020617 ↔ light #ffffff, pal.legendBox.\n stroke cyber #1f2937 ↔ light #e2e8f0, opacity\n 0.92 ↔ 0.97) still snapped on theme toggle. Same\n per-element 4-property easing R246 added to the\n per-node label card chrome — now applied at the\n panel scope so the whole panel (background + chrome\n + shadow) eases as one unit through theme switches. */\n filter: hoveredPanel === 'recent'\n ? (isLight ? 'drop-shadow(0 4px 12px rgba(15,23,42,0.14))'\n : 'drop-shadow(0 4px 12px rgba(0,0,0,0.65))')\n : (isLight ? 'drop-shadow(0 2px 6px rgba(15,23,42,0.08))'\n : 'drop-shadow(0 2px 6px rgba(0,0,0,0.45))'),\n transition: 'filter 200ms ease-out, fill 200ms ease-out, stroke 200ms ease-out, opacity 200ms ease-out',\n }}\n data-topo-panel-elevation=\"recent\"\n />\n {/* Round 266 / Loop: panel title fill picks up theme-toggle\n transition. Pre-R266 the title \"recent signal\" had\n fill={pal.legendHeadline} (cyber #e5e7eb ↔ light\n #0f172a) without any inline transition — so the BIGGEST\n text in the recent-signal panel (fontSize 12 fontWeight\n 700) hard-flipped color on theme toggle while the panel\n rect (R247) and every row inside (various) eased.\n Sibling treatment to the legend panel title at line\n ~6195 — the panel-pair's titles now ease together. */}\n {/* Round 301 / Loop: panel titles get letterSpacing=\"0.3\"\n for editorial parity with R289 watermark letterSpacing\n + R285 kicker tracking-widest. At fontSize 12 monospace\n fontWeight 700, default 0px letter-spacing reads as a\n code-style label; 0.3px gives it a touch of designed-\n header register without changing the lowercase\n terminal-style aesthetic. Sibling treatment applied\n to recent-signal panel title (here) and legend panel\n title (line ~6556) — both panels share the same\n editorial-text-spacing convention. data-recent-panel-\n title handle unchanged so R266 test still resolves. */}\n {/* Round 345 / Loop: recent-signal panel title gains\n letter-spacing tween 0.3 → 0.4 on panel hover.\n hoveredPanel === 'recent' is set by the panel <g>\n wrapper's onMouseEnter (line ~6263 area). Sibling to\n R344 hover-letter-spacing applied to the +N more\n flows footer — same gesture vocabulary at a panel-\n title scope: hovering the panel chrome spreads the\n title 0.1 px, signalling \"this is a coherent unit\n you're entering\". transition list extends letter-\n spacing 200ms ease-out alongside existing fill 200ms. */}\n <text x=\"13\" y=\"21\" fill={pal.legendHeadline} fontSize=\"12\" fontFamily=\"monospace\" fontWeight=\"700\" letterSpacing={hoveredPanel === 'recent' ? '0.4' : '0.3'} style={{ transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out' }} data-recent-panel-title>recent signal</text>\n {/* R96: header count now matches what the rows show. Pre-R96\n this read \"X msgs\" off the raw messages array, but the\n rows below render DEDUPED flowLinks — so a fleet with 10\n messages aggregating to 3 pairs read \"10 msgs\" above\n only 3 rows. Misreads as \"where are the other 7?\".\n \"X flows\" mirrors flowLinks.length one-for-one. When\n flows < msgs the chip-row's \"N active links · last 2s\"\n already tells the operator about traffic volume — no\n duplicate metric needed here.\n R129 / Loop: header gains an amber \" · N hot\" tail\n when ≥ 1 flowLink has count ≥ 10. The third surface\n of the hot-lane convention (R126 canvas badge / R127\n row count) lives at the panel header so a user\n scanning vertically — header → rows — gets a top-\n level summary before reading each row's amber digit.\n Restructured into a single <text> with <tspan>\n fragments so the amber portion can carry its own\n fill + weight without a sibling <text>. Switched\n anchor x=150 left-justified → x=217 right-justified\n so the count column unifies visually with the legend\n panel's right-justified header (line 3511 — also\n fontSize 10 monospace, also x≈215 textAnchor end).\n data-recent-panel-count stays on the flow tspan so\n the R96 / R128 tests still resolve. data-recent-\n panel-hot-count exposes the hot bucket count. */}\n {(() => {\n const hotFlowCount = flowLinks.filter(l => l.count >= 10).length;\n const hotStroke = isLight ? '#d97706' : '#fbbf24';\n // R162 / Loop: freshness tint on the panel-header\n // count tspan. R161 just colored the chip-row's \"N\n // active links · last 5s\" bullet by recency; the\n // recent-signal panel header is the panel-side\n // mirror of the same metric (flowLinks.length). Both\n // scopes now speak the same freshness vocabulary,\n // so a glance at either tells the operator whether\n // the network is firing right now. Four nested\n // scopes share one ladder:\n // canvas edge fade (R10)\n // row pip (R160)\n // chip bullet (R161)\n // panel header (R162, this round)\n // Same alpha ramp:\n // ageSec ≤ 30 → 1.0 (fully fresh)\n // 30-300s → smooth decay 1.0 → 0.25\n // > 300s → 0.25 stale floor\n // Hot tail (amber \" · N hot\" R129) is independent\n // of recency and keeps its own color — recency\n // tints the head; volume colors the tail.\n const recentMs = flowLinks.reduce<number | null>((acc, l) => {\n if (!l.last_at) return acc;\n const t = Date.parse(l.last_at);\n if (Number.isNaN(t)) return acc;\n return acc === null || t > acc ? t : acc;\n }, null);\n const ageSec = recentMs !== null\n ? Math.max(0, (Date.now() - recentMs) / 1000)\n : 999;\n const alpha = ageSec <= 30\n ? 1\n : ageSec <= 300\n ? 1 - ((ageSec - 30) / 270) * 0.75\n : 0.25;\n // Dark cyan-400 / light teal-600 with alpha — same\n // palette as R161's chip bullet so the two scopes\n // visually align even side-by-side.\n const freshFill = isLight\n ? `rgba(13, 148, 136, ${alpha.toFixed(2)})`\n : `rgba(34, 211, 238, ${alpha.toFixed(2)})`;\n return (\n <text\n x=\"217\" y=\"21\"\n textAnchor=\"end\"\n fontSize=\"10\"\n fontFamily=\"monospace\"\n >\n {/* Round 225 / Loop: tabular-nums on the panel-header\n flow-count tspan. The \"{N} flows\" string lives in\n a right-justified text anchor (x=217 textAnchor=\n 'end') so the BASELINE of the numeral is the same\n regardless of digit-count — but the SPACING between\n the digit and ' flows' label is monospace-jittery\n in the 1-digit → 2-digit boundary, and the ' · N\n hot' R190 tail that hangs off the end shifts by\n whatever the digit width delta is. Tabular-nums\n locks both, so the header reads stable through\n 9 flows → 10 flows growth. Sibling treatment to\n R224 edge badge / R225 hub digit. */}\n {/* Round 311 / Loop: recent-signal panel count tspan\n picks up fontWeight=600 for sibling parity with\n R310 legend panel count. Closes the panel-pair\n count typography symmetry — both top-corner\n panels now have:\n title fontWeight=700 (panel chrome anchor)\n count fontWeight=600 + tabular-nums (data)\n Same digit-semibold rule R309 established for\n per-row counts now applied to BOTH panel-summary\n counts. The R162 freshness fill (1.0→0.25 alpha\n ramp) and R225 tabular-nums all preserved; only\n the weight bumps. */}\n {/* Round 336 / Loop: split the digit from the unit\n word \" flows\" with a nested tspan at opacity=0.7.\n Same chip-internal-hierarchy pattern R333 (vendor\n count suffix) + R335 (filter pin prefix) applied\n to one chip — recurring \"small label spans demote,\n value stays prominent\" idiom at the panel-header\n count scope. The digit stays fw=600 + tabular-nums\n (R311 + R225 inheritance via the outer tspan);\n the unit tspan inherits fw=600 but adds opacity\n 0.7. Reads as \"5 (prominent) / flows (recessive\n unit)\". data-recent-panel-count attribute stays\n on the OUTER tspan so existing R311 fontWeight\n tests + count value reads still resolve via\n .textContent. data-recent-panel-count-unit on\n the inner unit tspan for R336 introspection. */}\n <tspan\n fill={freshFill}\n fontWeight=\"600\"\n data-recent-panel-count\n data-recent-panel-count-freshness-alpha={alpha.toFixed(2)}\n style={{\n transition: 'fill 200ms ease-out',\n fontVariantNumeric: 'tabular-nums',\n }}\n >{flowLinks.length}<tspan opacity=\"0.7\" data-recent-panel-count-unit> flows</tspan></tspan>\n {/* Round 190 / Loop: R129 hot-tail gets anet-fade-in\n for entrance. Pre-R190 the tspan snapped into\n the header the moment hotFlowCount crossed 0,\n and snapped out the moment it dropped back to 0.\n Same trade-off R67 accepts for filter pills:\n fade-IN smooth, accept exit snap. The CSS\n animation plays once when the tspan mounts\n (count goes 0 → 1+); subsequent re-renders\n (count growing from 1 → 2 → 3 hot flows)\n preserve the element via React reconciliation\n so the fade-in doesn't replay. Layout-shift\n cost is paid once on entrance — the parent\n <text textAnchor=\"end\"> recomputes its\n anchor as the tspan appears, then stays\n stable as the digit grows. Exit-snap is rare\n in steady operation: a hot flow cooling back\n below 10 messages doesn't happen often. */}\n {/* Round 223 / Loop: hot-tail tspan always-mounts;\n visibility crossfades via inline opacity gate\n instead of conditional mount/unmount on hot\n FlowCount > 0. Pre-R223 R190's anet-fade-in gave\n a smooth entrance but exit was snap (the family's\n original \"fade-IN smooth, accept exit snap\" trade-\n off). R223 closes the asymmetry — both directions\n now ease 300ms. anet-fade-in className kept for\n R190 test compat (plays once on initial render\n if the page loads with hotFlowCount > 0); subsequent\n threshold crossings use the opacity transition\n bi-directionally. Text content gates to empty\n string when hidden so the parent <text textAnchor=\n \"end\"> doesn't compute anchor against stale \"·\"\n separator. data-recent-panel-hot-visible exposes\n the gate for tests. Always-mount-opacity-gate\n family now hits 10 surfaces (R181/R182/R183/R213×2/\n R214/R215/R221/R222/R223). */}\n {/* Round 322 / Loop: hot count tspan picks up\n fontVariantNumeric tabular-nums for parity with\n its left-sibling tspan (R311 `{flowLinks.length}\n flows`, already tabular). Pre-R322 a hotFlowCount\n crossing 1→10 widened the leading digit and (since\n the parent <text> is textAnchor=\"end\") shifted the\n WHOLE header left a few pixels — visible micro-\n jitter against the panel rect's left edge. Tabular-\n nums locks the digit so the right-anchored block\n stays stable as hotFlowCount grows. 8th surface\n in the info-density tabular-nums sweep:\n R224 edge badge / R225 hub digit / R225 panel\n flows-count + recent-row count / R229 group-\n label count / R230 group-label status pips /\n R320 recent-row count fw=600 (left neighbour) /\n R321 recent-row timestamp / R322 panel hot\n count (this round). */}\n <tspan\n fill={hotStroke}\n fontWeight=\"700\"\n data-recent-panel-hot-count={hotFlowCount}\n data-recent-panel-hot-visible={hotFlowCount > 0 ? 'true' : 'false'}\n className=\"anet-fade-in\"\n opacity={hotFlowCount > 0 ? 1 : 0}\n style={{\n transition: 'opacity 300ms ease-out',\n fontVariantNumeric: 'tabular-nums',\n }}\n >\n {/* Round 336 / Loop: split hot count from \" hot\"\n unit word with nested opacity-0.7 tspan. Same\n chip-internal-hierarchy idiom this round\n applies to the \"{N} flows\" tspan above (R311\n sibling) — digit prominent at fw=700 + amber\n fill, unit recessive at 0.7 opacity. data-\n recent-panel-hot-count-unit exposes the unit\n tspan for R336 probes. */}\n {hotFlowCount > 0 ? (\n <>\n {` · ${hotFlowCount}`}\n <tspan opacity=\"0.7\" data-recent-panel-hot-count-unit> hot</tspan>\n </>\n ) : ''}\n </tspan>\n </text>\n );\n })()}\n {/* Round 45 / Loop: empty state. The panel used to render\n \"recent signal\" + \"0 msgs\" with three blank slots below\n when no flow yet — read as \"broken\" rather than \"quiet\".\n A muted centred placeholder makes the empty state\n deliberate. Messages count CAN diverge from flowLinks\n count (raw count vs. deduped pairs), so the placeholder\n fires on flowLinks.length=0 specifically. */}\n {/* Round 222 / Loop: empty state always-mounts; visibility\n crossfades via wrapper <g> opacity instead of conditional\n mount/unmount on flowLinks.length === 0. Pre-R222 the\n first flow arriving snap-removed the empty state in one\n frame while R203 rows simultaneously faded IN — half-\n smooth, half-snap on this \"first data\" first-impression\n moment. R222 closes the snap so empty fades OUT while\n rows fade IN — a proper crossfade for the user's most\n emotionally-loaded moment (the empty-to-populated flip).\n\n R200 SMIL breath on each text continues running\n regardless of parent opacity (SVG opacity is\n multiplicative — same R214 pulse-dot composition idiom).\n When parent opacity is 0, SMIL still animates child\n opacity but the result composes to invisible. CSS and\n SMIL don't fight, they layer.\n\n 300ms transition matches R203 row fade-in pace so the\n empty-fade-out + rows-fade-in pair share rhythm during\n the crossfade. */}\n <g\n data-recent-signal-empty-wrapper\n data-recent-signal-empty-visible={flowLinks.length === 0 ? 'true' : 'false'}\n style={{\n opacity: flowLinks.length === 0 ? 1 : 0,\n transition: 'opacity 300ms ease-out',\n pointerEvents: 'none',\n }}\n >\n {/* Round 258 / Loop: re-center the empty state within the\n post-R256 88-tall panel. R45 placed \"no flow yet\" at\n y=54 + the hint at y=68 inside an 84-tall panel — those\n baselines sat 12px / 26px below the panel mid-line at\n y=42, optically balanced for the original height. R256\n grew the panel 84 → 88 to give the \"+N more flows\"\n footer underline breathing room, shifting the panel\n mid-line to y=44 — but the empty state stayed put,\n drifting 2px high. R258 pushes both empty-state lines\n +2 (main y=54 → y=56, hint y=68 → y=70) so the pair\n sits 12px / 26px below the new mid-line, restoring\n the R45 optical balance. The R222 always-mount opacity\n gate is unaffected (geometry-only shift), and the\n R200 SMIL breath continues unchanged. Hint baseline\n y=70 still sits 12px above the footer baseline (y=82),\n same vertical rhythm as the row 3 → footer gap. */}\n {/* Round 302 / Loop: empty-state hint 'no flow yet' picks\n up letterSpacing='0.2' for editorial parity with R301\n panel titles (0.3) + R285 kicker tracking-widest +\n R289 watermark letterSpacing. The hint is the\n panel's quietest authored text (italic monospace\n fontSize 10 opacity 0.65); adding a small positive\n letter-spacing keeps it in the same designed-label\n family without lifting its visual weight. 0.2px is\n slightly less than the panel titles' 0.3 — appropriate\n for the smaller fontSize + lower opacity (empty-state\n is intentionally quieter than the header above it). */}\n <text\n x=\"115\" y=\"56\" textAnchor=\"middle\"\n fill={pal.legendText}\n fontSize=\"10\" fontFamily=\"monospace\" fontStyle=\"italic\"\n letterSpacing=\"0.2\"\n opacity={0.65}\n data-recent-signal-empty\n data-recent-signal-empty-breathes={reducedMotion ? 'false' : 'true'}\n >\n no flow yet\n {!reducedMotion && (\n <animate\n attributeName=\"opacity\"\n values=\"0.55;0.78;0.55\"\n dur=\"4.4s\"\n repeatCount=\"indefinite\"\n />\n )}\n </text>\n {/* Round 259 / Loop: instructional hint bumps fontSize 8 → 9\n for readability. Pre-R259 the empty-state hint was at\n the smallest readable size on the canvas (8pt), with\n italic + opacity 0.45 layering legibility cost on top\n — instructional text users need to READ to act on,\n yet eye-straining at default 1× zoom. 9pt italic stays\n visually subordinate to the 10pt main \"no flow yet\"\n AND to the 9pt regular row text (italic alone\n discriminates from row content) while easing the\n legibility floor. Sibling change at the +N-more\n footer link (line ~6047) applies the same bump to\n the panel's other italic secondary text. Per-row\n timestamp at y=38+i*16 (fontSize 8 right-edge\n recency tag) STAYS at 8 — it's an at-a-glance\n recency tag tightly co-located with row text, not\n read-to-act instruction. */}\n {/* Round 304 / Loop: secondary instructional hint\n 'send a message between agents' gets letterSpacing\n '0.15'. Extends the R301/R302 editorial-spacing\n family one layer down. The hint is the quietest\n authored text in the recent-signal panel (fontSize\n 9 italic-less opacity 0.45, sits below the R302\n main empty-state hint at fontSize 10 italic\n opacity 0.65). 0.15px is below R302's 0.2px to\n match the visual hierarchy: smaller + quieter\n text gets less letter-spacing.\n 5-axis editorial-letterspacing hierarchy now:\n R285 kicker: 1.2px (eyebrow loud)\n R289 watermark: 0.5px (wordmark brand)\n R301 panel titles: 0.3px (section headers)\n R302 empty main: 0.2px (empty-state hint)\n R304 empty hint: 0.15px (instructional sub)\n Each step ~0.1-0.5x scale-down matches the\n font-size + opacity descent. */}\n {/* Round 339 / Loop: empty-state sub-hint picks up\n fontStyle=\"italic\" for parity with the main hint\n above (line ~6526). Pre-R339 the main hint \"no\n flow yet\" was italic while the sub-hint \"send a\n message between agents\" was upright — two empty-\n state texts sharing the same quiet informational\n role but rendered in different styles. R339 closes\n the inconsistency: both texts now read as deliberate\n italic empty-state messaging. The R304 letter-\n spacing 0.15 + R259 fontSize 9 + opacity 0.45 +\n SMIL opacity breath all preserved. */}\n <text\n x=\"115\" y=\"70\" textAnchor=\"middle\"\n fill={pal.legendText}\n fontSize=\"9\" fontFamily=\"monospace\" fontStyle=\"italic\"\n letterSpacing=\"0.15\"\n opacity={0.45}\n data-recent-signal-empty-hint\n data-recent-signal-empty-hint-breathes={reducedMotion ? 'false' : 'true'}\n >\n send a message between agents\n {!reducedMotion && (\n <animate\n attributeName=\"opacity\"\n values=\"0.36;0.58;0.36\"\n dur=\"4.4s\"\n begin=\"-1.5s\"\n repeatCount=\"indefinite\"\n />\n )}\n </text>\n </g>\n {flowLinks.length === 0 ? null : (\n // Round 56 / Loop: each row is a navigator into the canvas.\n // Hover a row → set hoveredEdgeKey, which the existing R50\n // edge-focus + R49 endpoint-highlight ladders consume. The\n // matching flow edge brightens to 2× + thickens, its two\n // endpoint nodes stay full opacity, and every other edge +\n // non-endpoint node dims. Released → all restore. Wrapping\n // <g> + a transparent 218×14 hitbox so the cursor doesn't\n // have to land precisely on the truncated text.\n flowLinks.slice(0, 3).map((link, index) => {\n // Round 94 / Loop: per-row relative timestamp. The chip\n // row shows \"last 2s\" for the most recent flow overall,\n // but a user scanning the recent-signal panel had no way\n // to tell whether row 2 was 5s old or 5m old without\n // hovering nodes. Compact `2s` / `1m` glyph at the\n // right edge — same relativeAgo helper R42 uses for\n // the chip — pulls double duty as a recency anchor and\n // a sortedness hint (top row is freshest by construction).\n // Strip the \" ago\" suffix — at fontSize=8 in a 32-px\n // right-edge slot, every char counts. \"30s ago\" → \"30s\".\n const rawAt = link.last_at ? relativeAgo(link.last_at) : null;\n const lastAt = rawAt ? rawAt.replace(/\\s+ago$/, '') : null;\n const isRowHovered = hoveredEdgeKey === link.key;\n const isRowPinned = pinnedEdgeKey === link.key;\n const isRowActive = isRowHovered || isRowPinned;\n // Round 191 / Loop: timestamp text on row's right edge\n // picks up the R160 row-pip freshness ramp at a\n // different alpha range — gives the timestamp the same\n // visual recency encoding the pip has on the LEFT\n // edge, so both ends of the row mirror each other.\n // ≤30s → opacity 0.85 (fresh, high contrast)\n // 30-300s → 0.85 → 0.30 (smooth decay)\n // >300s → 0.30 (stale floor)\n // Pre-R191 the timestamp was static 0.55 — the same\n // visual weight whether the message just fired or\n // happened 5 minutes ago. Fill stays legendText gray\n // (not cyan) because the left pip already carries the\n // cyan; the right timestamp encodes recency through\n // contrast against the dark canvas, not hue.\n const tsAlpha = !link.last_at ? 0.55 : (() => {\n const ageSec = Math.max(0, (Date.now() - Date.parse(link.last_at)) / 1000);\n return ageSec <= 30 ? 0.85\n : ageSec <= 300 ? 0.85 - ((ageSec - 30) / 270) * 0.55\n : 0.30;\n })();\n // R127: panel-side mirror of R126's canvas hot-badge.\n // The recent-signal row text packs `alias→alias / N /\n // preview` into one line, with N rendered identically\n // regardless of magnitude. Now that the canvas badge\n // tells the user \"≥ 10 msgs = hot lane\" via amber\n // stroke, the panel row needs the same affordance so\n // the user reading the list at a glance can spot hot\n // lanes without crossing to the canvas. Renders the\n // count digit in amber + 700-weight when isHot; the\n // surrounding alias text + separators stay in the\n // existing legendText/legendHeadline palette. Reuses\n // R126's hotStroke colour for visual consistency.\n const isHot = link.count >= 10;\n const hotStroke = isLight ? '#d97706' : '#fbbf24';\n return (\n <g\n key={link.key}\n data-recent-row={link.key}\n data-recent-row-hovered={isRowHovered ? 'true' : 'false'}\n data-recent-row-pinned={isRowPinned ? 'true' : 'false'}\n data-recent-row-hot={isHot ? 'true' : 'false'}\n data-recent-row-lifted={(isRowHovered || isRowPinned) ? 'true' : 'false'}\n // Round 203 / Loop: per-row mount fade-in. R175 already\n // eased the whole panel in once, but new flows rising\n // INTO the top-3 list (or replacing an older row) snap-\n // popped in. React reconciliation via key={link.key}\n // preserves stable rows across re-renders, so anet-\n // fade-in only plays on mount — never replays when\n // counts update or rows reorder by recency. Stacks on\n // the panel's own R175 anet-fade-in: SVG opacity\n // composes multiplicatively, so during the first paint\n // the panel's 700ms delay holds rows hidden until the\n // panel reveals, then row opacity transitions inside\n // the visible panel. For mid-session arrivals (panel\n // already at opacity 1) the row's 150ms fade-in plays\n // standalone. Three layers of mount-once eases now\n // share rhythm: panel (R175) → rows (R203) → row\n // contents (existing R160 pip / R191 ts opacity\n // ramps animate independently after mount).\n className=\"anet-topo-svg-focus anet-fade-in\"\n role=\"button\"\n tabIndex={0}\n aria-pressed={isRowPinned}\n // R143 / Loop: extend the R135/R142 \"interactive surface\n // elevates\" idiom down one layer to the recent-signal\n // panel rows. R104 already tints the row background on\n // hover; R143 adds a 1-px translate so the row text\n // visually lifts off the panel — same vocabulary R51\n // uses for nodes, R135 uses for panels, R142 uses for\n // group boxes. Pinned rows lift too (sticky state\n // should look like locked-in selection). Reduced-motion\n // safe via prefers-reduced-motion blanket override\n // applied to transition-duration in globals.css.\n style={{\n cursor: 'pointer',\n transform: (isRowHovered || isRowPinned) ? 'translateY(-1px)' : undefined,\n transition: 'transform 150ms cubic-bezier(0.4, 0, 0.2, 1)',\n }}\n onPointerDown={(e) => e.stopPropagation()}\n onMouseEnter={() => setHoveredEdgeKey(link.key)}\n onMouseLeave={() => setHoveredEdgeKey(prev => prev === link.key ? null : prev)}\n // R116: click toggles pin. activeEdgeKey =\n // hoveredEdgeKey ?? pinnedEdgeKey so the matching\n // edge stays \"hot\" after mouseleave; click again\n // (or Esc) releases.\n onClick={() => setPinnedEdgeKey(prev => prev === link.key ? null : link.key)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setPinnedEdgeKey(prev => prev === link.key ? null : link.key);\n }\n }}\n >\n {/* R148 / Loop: row tooltip with full message context.\n The row text truncates aliases to 6 chars (R127)\n and content to 8 chars — useful for scan-density\n but obscures the underlying message. A native SVG\n <title> reveals the full alias / content /\n timestamp on hover. Pinned vs unpinned switches\n the click hint so the user knows the next\n gesture's effect. R98 enriched the node tooltip\n the same way for the source/destination scope;\n R148 brings the per-row equivalent to the panel\n side. */}\n <title>{[\n `${link.from} → ${link.to} · ${link.count} msg${link.count === 1 ? '' : 's'}${isHot ? ' (hot lane · ≥ 10)' : ''}`,\n link.last_at ? `last: ${new Date(link.last_at).toLocaleString()}` : null,\n link.content ? `\"${link.content}\"` : null,\n isRowPinned ? 'click to release pin (Esc to clear)' : 'click to pin · hover to preview',\n ].filter(Boolean).join('\\n')}</title>\n {/* R104: subtle row-background tint on hover. R56\n already brightens the matching edge on the canvas,\n but the panel row itself stayed flat — felt more\n like text-with-handlers than a navigable list.\n Filling the rect at hover with `pal.legendAccent`\n at low alpha gives the row visual feedback at the\n source surface, mirroring the list-item idiom from\n the chip-row pills. R116: pinned rows tint\n stronger than hovered ones so locked vs preview\n is discriminable. */}\n <rect\n x=\"6\" y={38 + index * 16 - 10}\n width=\"218\" height=\"14\" rx=\"3\"\n fill={isRowActive ? pal.legendAccent : 'transparent'}\n opacity={isRowPinned ? (isLight ? 0.18 : 0.22)\n : isRowHovered ? (isLight ? 0.10 : 0.14)\n : 1}\n style={{ transition: 'fill 150ms ease-out, opacity 150ms ease-out' }}\n />\n {/* Round 160 / Loop: recency pip. Canvas flow edges\n fade by freshness (R10: full intensity ≤30s →\n ~35% over 5min). The recent-signal panel rows\n duplicate that data (alias→alias · N · 5s)\n but encode freshness purely in text — no\n at-a-glance visual cue for \"which row is\n actively firing right now\". A 1.6-px cyan dot\n at x=10 (in the 7-px margin between rect-\n start x=6 and text-start x=13) brightens\n fresh rows and dims stale ones — same\n vocabulary the canvas uses, brought to the\n panel side.\n\n Three encodings now coexist on each row,\n none competing:\n rect fill = hover/pin state (R104/R116)\n count tspan = magnitude (R127 amber when ≥10)\n pip = recency (this round)\n\n Geometry: cy = row_y - 3 (mid-row vertical\n centre, where text baseline at y=row_y sits\n slightly below). r=1.6 fits cleanly in the\n 7-px left margin. pointerEvents:none so the\n row's button-role hit area is unchanged.\n No overlap-test impact (entirely within the\n existing rect bbox). */}\n {(() => {\n if (!link.last_at) return null;\n const ageSec = Math.max(0, (Date.now() - Date.parse(link.last_at)) / 1000);\n // 0-30s: fully fresh (1.0). 30-300s: smooth\n // decay 1→0.25. >300s: stale floor (0.25).\n const alpha = ageSec <= 30\n ? 1\n : ageSec <= 300\n ? 1 - ((ageSec - 30) / 270) * 0.75\n : 0.25;\n return (\n <circle\n cx={10}\n cy={38 + index * 16 - 3}\n r={1.6}\n fill={pal.legendAccent}\n opacity={alpha}\n data-recent-row-freshness={link.key}\n data-recent-row-freshness-alpha={alpha.toFixed(2)}\n style={{ pointerEvents: 'none', transition: 'opacity 200ms ease-out' }}\n />\n );\n })()}\n {/* Round 220 / Loop · milestone: recent-signal row\n text completes the pin-signature typography\n triple (R218 group labels / R219 legend rows /\n R220 recent-signal rows). All three label-based\n interactive surfaces now read \"locked in\" at\n the type level when pinned — letter-spacing\n spreads 0px → 0.5px on isRowPinned (NOT on\n hover — hover keeps default tracking so the\n eye can discriminate transient preview from\n sticky pin without checking chrome). Pin\n signature vocabulary now consistent across\n the entire interactive-label landscape of\n TopoGraph: every pin-able text element has\n a typography-level tell.\n transition extends 'letter-spacing 150ms'\n alongside R55 fill 150ms — same beat as\n R219 legend-row treatment. Hover still keeps\n its own R55 fill brighten exclusively;\n letter-spacing is pin-exclusive (note the\n isRowPinned not isRowActive gate). */}\n <text\n x=\"13\" y={38 + index * 16}\n fill={isRowActive ? pal.legendHeadline : pal.legendText}\n fontSize=\"9\"\n fontFamily=\"monospace\"\n data-recent-row-text={link.key}\n data-recent-row-text-pinned={isRowPinned ? 'true' : 'false'}\n style={{\n transition: 'fill 150ms ease-out, letter-spacing 150ms ease-out',\n letterSpacing: isRowPinned ? '0.5px' : '0px',\n }}\n >\n {/* R138 / Loop: typography unification with the rest\n of the topology UI. Filter pills (R119) render\n \"{from}→{to}\", node tooltips (R98) use →, the\n active-links chip tooltip (R114) and edge-badge\n titles all use unicode →. The recent-signal row\n was the lone holdout still rendering \"from -> to\"\n in ASCII. The data delimiter likewise: filter\n pills use \" · \" (\"status · 3\"); the row was using\n \" / \". Both swaps make the row read like every\n other surface — one less micro-style to remember. */}\n {truncate(link.from, 6)} {'→'} {truncate(link.to, 6)} {' · '}\n {/* Round 189 / Loop: count tspan unified — pre-R189\n two different tspans (data-recent-row-count vs\n data-recent-row-count-hot) mounted/unmounted\n on the R127 hot threshold (count >= 10),\n making fill (legendText ↔ amber) + fontWeight\n (regular ↔ 700) snap one-frame. Now one tspan\n always-mounted; isHot drives fill/fontWeight\n conditionally. style.transition='fill 300ms\n ease-out' makes the hot crossing ease through\n the colour shift — same vocabulary R188 just\n added to the edge midpoint badge stroke (the\n panel-side mirror of that surface). fontWeight\n stays binary (no clean weight interpolation\n across browsers). data-recent-row-count\n continues to expose the tspan to existing\n tests; data-recent-row-count-hot becomes\n an attribute on the same element when active\n so legacy probes still resolve. */}\n {/* Round 225 / Loop: tabular-nums on the per-row\n count digit. The row text reads \"alpha → beta ·\n {count} · content\"; when {count} grows from a\n single digit to two (9 → 10) the subsequent\n \" · {content}\" preview slides ~3-4px right in\n monospace because '1' and '0' have different\n natural widths against the surrounding control\n glyphs even in mono fonts. Tabular-nums locks\n the count column so the content preview\n column stays planted as activity scales up.\n Sibling treatment to R224 edge badge / R225\n hub digit / R225 panel-header flow-count. */}\n {/* Round 320 / Loop: cold-state per-row count gains\n explicit fontWeight=\"600\" instead of inheriting\n the parent <text>'s default (400). Brings the\n recent-signal row count into the 5-tier SVG\n data-weight family established by R309 (legend\n per-row count) / R310 (legend panel-header\n count) / R311 (recent-signal panel-header flow\n count). Pre-R320 the per-row count `· 12` for\n a cold row painted at fw=400, identical weight\n to the surrounding aliases — the count digit\n should read as data and stand out from the\n alias text. Hot crossing stays at fw=700 (R127),\n so cold→hot delta becomes 600→700 (still\n distinct, plus the fill flip from legendText\n → amber carries the dramatic part of the cue).\n Sibling treatment in the data-weight tier. */}\n <tspan\n fill={isHot ? hotStroke : undefined}\n fontWeight={isHot ? '700' : '600'}\n data-recent-row-count\n {...(isHot ? { 'data-recent-row-count-hot': 'true' } : {})}\n style={{\n transition: 'fill 300ms ease-out',\n fontVariantNumeric: 'tabular-nums',\n }}\n >\n {link.count}\n </tspan>\n {' · '}{truncate(link.content, 8)}\n </text>\n {lastAt ? (\n /* Round 321 / Loop: lastAt freshness timestamp picks\n up fontVariantNumeric tabular-nums. The string\n marches through 1s..59s (1 digit / 2 digits) /\n 1m..59m / 1h..24h every second the panel ticks,\n and the textAnchor=\"end\" right-aligns against\n x=217. Pre-R321 a 9s→10s crossing slid the chip\n left ~3px in monospace (digit '1' narrower than\n '0' even in mono) — same one-frame visible jitter\n R225 / R230 fixed elsewhere. Tabular-nums locks\n the digit slot so the timestamp stays planted as\n seconds tick. 7th surface in the info-density\n tabular-nums sweep after R224 edge badge / R225\n hub digit + panel header + recent row count /\n R229 group-label count / R230 group-label\n status pips / R320 recent-row count fw=600\n (count and timestamp now both lock). */\n <text\n x=\"217\" y={38 + index * 16}\n textAnchor=\"end\"\n fill={pal.legendText}\n fontSize=\"8\"\n fontFamily=\"monospace\"\n opacity={tsAlpha}\n data-recent-row-ts={link.key}\n data-recent-row-ts-alpha={tsAlpha.toFixed(2)}\n style={{\n pointerEvents: 'none',\n transition: 'opacity 200ms ease-out',\n fontVariantNumeric: 'tabular-nums',\n }}\n >\n {lastAt}\n </text>\n ) : null}\n </g>\n );\n })\n )}\n {/* Round 128 / Loop: overflow hint. The recent-signal panel\n renders the top 3 flowLinks via .slice(0, 3) — but a\n fleet with 5 or 10 active flows silently truncates the\n rest. The R96 \"X flows\" header tells the total but\n doesn't say \"you're seeing top-3\". This hint fires\n only when flowLinks.length > 3 so quiet fleets stay\n clean. Footer y=82 sits between row 3 (baseline 70)\n and the panel bottom; the R256 height bump 84→88\n adds 6 px of clear below the footer baseline so the\n on-hover textDecoration:underline (which renders\n ~3-4 px below baseline → y≈85-86) tucks INSIDE the\n panel border instead of clipping past it at the\n old 84-px floor. Overlap-test geometry unchanged\n (panel selector at translate(16,16); corner-to-\n center distance 342.6 → 340 still > 325 ring-clear\n threshold). fontStyle=italic + opacity 0.55 reads\n as muted metadata, not an actionable row — matches\n the R110 empty-state hint idiom. */}\n {(() => {\n // Round 221 / Loop: footer always-mounts; visibility\n // crossfades via wrapper <g> opacity instead of React\n // conditional mount/unmount on flowLinks.length > 3.\n // Pre-R221 a fleet's 4th flow appearing snap-popped the\n // footer in; tapering back to 3 flows snap-removed it.\n // Same threshold-crossing snap R215 closed for edge\n // midpoint badges, now applied to the recent-panel\n // footer surface. moreCount clamps at 0 so when invisible\n // (length ≤ 3) the data attribute and text don't show\n // garbage negative numbers. a11y trio (role / tabIndex /\n // aria-hidden) and pointerEvents follow visibility so\n // the hidden footer doesn't appear in tab order or\n // intercept clicks at its midpoint coordinates.\n const visible = flowLinks.length > 3;\n const moreCount = Math.max(0, flowLinks.length - 3);\n const label = `+ ${moreCount} more flow${moreCount === 1 ? '' : 's'}`;\n // R133: the truncation hint becomes a clickable nav to\n // /messages. R128 introduced the footer as pure metadata\n // (\"you're seeing top-3\"); R133 closes the gap by giving\n // users a way to ACT on that info — see the full list.\n // Wrap in <g> with onClick so SVG hit-testing fires the\n // route push. cursor:pointer + the underline-on-hover\n // visual cue tells users this is interactive. The hover\n // state is React-controlled (no CSS :hover on SVG <g>\n // descendant text would feel reliable across Chrome's\n // SVG quirks). pointerEvents follows visibility (R215\n // pattern) so the hidden footer can't intercept clicks\n // at its midpoint when it's invisible (sub-threshold\n // fleets).\n return (\n <g\n data-recent-panel-more-nav\n data-recent-panel-more-visible={visible ? 'true' : 'false'}\n role={visible ? 'link' : undefined}\n tabIndex={visible ? 0 : -1}\n aria-hidden={visible ? undefined : true}\n className=\"anet-topo-svg-focus\"\n style={{\n cursor: visible ? 'pointer' : undefined,\n pointerEvents: visible ? 'all' : 'none',\n opacity: visible ? 1 : 0,\n transition: 'opacity 300ms ease-out',\n }}\n onPointerDown={(e) => e.stopPropagation()}\n onMouseEnter={() => { if (visible) setHoveredRecentMore(true); }}\n onMouseLeave={() => setHoveredRecentMore(false)}\n onClick={() => { if (visible) router.push('/messages'); }}\n onKeyDown={(e) => {\n if (!visible) return;\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n router.push('/messages');\n }\n }}\n >\n <title>{`${label} — open /messages for the full list`}</title>\n {/* Round 195 / Loop: footer hint adopts the cyan\n vocabulary on hover, joining the interactive-on-\n hover-is-cyan family (R178 fullscreen / R163\n layout / R179 nodeSize / R193 active-links chip).\n Pre-R195 the footer brightened on hover via\n opacity + underline but kept its gray fill —\n 'becomes brighter gray' rather than 'becomes the\n go-act color'. legendAccent is the same cyan-400\n (dark) / teal-600 (light) every other interactive\n hover speaks. transition list extends `fill 200ms\n ease-out` so the colour swap eases instead of\n snapping. data-recent-panel-more-hovered exposes\n the gate for tests. */}\n {/* Round 259 / Loop: footer link bumps fontSize 8 → 9\n for clickable-text readability. Pre-R259 the\n \"+N more flows\" footer (the panel's primary\n navigation affordance into /messages) sat at the\n same 8pt as the empty-state hint — small enough\n to make the click target feel cheap. 9pt + italic\n + opacity 0.55 keeps it visually secondary to row\n content (9pt regular) while giving the link\n enough type-weight to read as a real affordance.\n Underline geometry verified: with fontSize 9 the\n underline still renders ~3-4px below baseline at\n y=82 → underline ~y=85-86, panel bottom y=88 →\n 2-3px clear (R256 footer-breath invariant\n preserved). */}\n {/* Round 325 / Loop: footer link joins the editorial\n letter-spacing family at 0.2px — same axis as\n R302 empty-state main hint (fontSize 10 opacity\n 0.65). The footer is italic monospace fontSize 9\n opacity 0.55 acting as the panel's primary\n navigation affordance into /messages; pre-R325\n it sat orphaned from the R285/R289/R301/R302/R304\n editorial-spacing axis even though it carried\n the same designed-label semantics (action label,\n not row data). 0.2px is the same value as R302\n because at fontSize 9 vs 10 the visual density\n of \"+ N more flows\" is close to \"no flow yet\"\n and they're both italic — sibling-equal in the\n hierarchy. The R195 cyan-hover + R259 fontSize\n bump stay; this round only adds the spacing.\n 6-axis editorial-letterspacing hierarchy now:\n R285 kicker: 1.2px (eyebrow loud)\n R289 watermark: 0.5px (wordmark brand)\n R301 panel titles: 0.3px (section headers)\n R302 empty main: 0.2px (empty-state hint)\n R325 footer link: 0.2px (panel nav action) ← NEW\n R304 empty sub: 0.15px (instructional sub) */}\n {/* Round 340 / Loop: +N more flows footer link extends\n the R333/R335/R336/R337/R338 chip-internal-hierarchy\n arc to a 6th surface. The digit `{moreCount}` reads\n as the primary data (\"how many more flows\"); the\n unit text ` more flow(s)` recedes via a nested\n tspan with opacity-0.7 (multiplicative against the\n parent <text>'s hover/rest opacity, so unit always\n sits below the digit). The `label` variable is\n preserved for the <title> tooltip — only the SVG\n render splits. data-recent-panel-more-unit exposes\n the unit tspan for tests. */}\n {/* Round 344 / Loop: footer hover gains letter-spacing\n tween 0.2 → 0.3. R325 set rest letter-spacing\n 0.2 to join the editorial-spacing family; R344\n adds a 0.1px hover spread that layers on top of\n R195 cyan fill + R325 spacing + R133 underline\n so the footer reads \"lit up and spaced\" on\n hover — sibling to R218/R219/R220 pin-signature\n letter-spacing family applied to a hover-only\n surface. transition list extends letter-spacing\n 200ms ease-out alongside the existing opacity/\n fill easings. */}\n <text\n x=\"115\" y=\"82\"\n textAnchor=\"middle\"\n fill={hoveredRecentMore ? pal.legendAccent : pal.legendText}\n fontSize=\"9\"\n fontFamily=\"monospace\"\n fontStyle=\"italic\"\n letterSpacing={hoveredRecentMore ? '0.3' : '0.2'}\n opacity={hoveredRecentMore ? 0.85 : 0.55}\n textDecoration={hoveredRecentMore ? 'underline' : 'none'}\n data-recent-panel-more={moreCount}\n data-recent-panel-more-hovered={hoveredRecentMore ? 'true' : 'false'}\n style={{ transition: 'opacity 150ms ease-out, fill 200ms ease-out, letter-spacing 200ms ease-out' }}\n >\n {`+ ${moreCount}`}\n <tspan opacity=\"0.7\" data-recent-panel-more-unit>{` more flow${moreCount === 1 ? '' : 's'}`}</tspan>\n </text>\n </g>\n );\n })()}\n </g>\n )}\n\n {/* legend — Round 55 / Loop: each status row is now a hover\n target. Pointer enter sets `hoveredStatus`; pointer leave\n clears it. Node opacity formula composes the match below.\n The row text brightens to legendHeadline while hovered as\n a small affordance hint. Geometry unchanged — the new\n <g> wrappers only carry pointer handlers. */}\n <g\n transform=\"translate(760, 16)\"\n data-topo-panel=\"legend\"\n data-topo-panel-hovered={hoveredPanel === 'legend' ? 'true' : 'false'}\n // R175 / Loop: legend panel offset 100ms behind the\n // recent-signal panel so the two corner panels cascade\n // left-then-right rather than appearing in lockstep.\n // Same .anet-fade-in mechanism the four wave layers use.\n className=\"anet-fade-in\"\n data-topo-panel-fade-delay={800}\n style={{ animationDelay: '800ms' }}\n onMouseEnter={() => setHoveredPanel('legend')}\n onMouseLeave={() => setHoveredPanel(prev => prev === 'legend' ? null : prev)}\n >\n {/* R57: matching drop-shadow elevation to the legend panel.\n R106: panel height grew 96 → 104 to seat the new header\n line + 4 px row-shift below it (so the new header text\n doesn't overlap the row-1 hitbox region).\n R135: hover-elevation mirrors the recent-signal panel\n rect at line ~3299. Both panels grow their shadow on\n hover to telegraph \"the chrome is interactive\" since\n their rows pin / nav. */}\n {/* Round 247 / Loop: sibling treatment to the recent-signal\n panel — fill + stroke + opacity transitions added so\n the legend panel also eases through theme toggles\n (no snap on cyber↔light switch). Same 200ms cadence\n across the panel pair. */}\n {/* Round 331 / Loop: legend panel rect rx 8 → 10 — sibling\n treatment to the recent-signal panel above. Same\n proportional-rhythm step under R330's rounded-xl\n canvas wrapper envelope. */}\n <rect\n x=\"0\" y=\"0\" width=\"224\" height=\"88\" rx=\"10\"\n fill={pal.legendBox.fill}\n stroke={pal.legendBox.stroke}\n // R348 sibling — legend panel rect opacity hover-state\n // bump 0.92 → 0.97 (cyber) / 0.97 → 1 (light) on\n // hoveredPanel === 'legend'. Pairs with the recent-signal\n // panel rect above so the two corner panels' hover cues\n // stay symmetric. Geometry-safe (paint-only).\n opacity={hoveredPanel === 'legend' ? (isLight ? 1 : 0.97) : (isLight ? 0.97 : 0.92)}\n style={{\n filter: hoveredPanel === 'legend'\n ? (isLight ? 'drop-shadow(0 4px 12px rgba(15,23,42,0.14))'\n : 'drop-shadow(0 4px 12px rgba(0,0,0,0.65))')\n : (isLight ? 'drop-shadow(0 2px 6px rgba(15,23,42,0.08))'\n : 'drop-shadow(0 2px 6px rgba(0,0,0,0.45))'),\n transition: 'filter 200ms ease-out, fill 200ms ease-out, stroke 200ms ease-out, opacity 200ms ease-out',\n }}\n data-topo-panel-elevation=\"legend\"\n />\n {/* R106 / Loop: panel header — symmetric with the recent-\n signal panel's \"recent signal · N flows\" (R96). Same\n font + position vocabulary so the two side panels feel\n paired. Title text at x=13 y=21; total fleet count\n right-aligned at x=215 y=21 in the accent colour. */}\n {/* Round 266 / Loop: legend panel title fill picks up theme-\n toggle transition — sibling treatment to the recent-\n signal panel title at line ~5459. Pre-R266 both panel\n titles hard-flipped color on theme toggle while their\n surrounding chrome eased; R266 closes both at once. */}\n {/* R301: sibling to recent-signal panel title above —\n same letterSpacing 0.3 for editorial parity. */}\n {/* R345 sibling — legend panel title same hover letter-\n spacing tween 0.3 → 0.4 on panel hover. */}\n <text x=\"13\" y=\"21\" fill={pal.legendHeadline} fontSize=\"12\" fontFamily=\"monospace\" fontWeight=\"700\" letterSpacing={hoveredPanel === 'legend' ? '0.4' : '0.3'} style={{ transition: 'fill 200ms ease-out, letter-spacing 200ms ease-out' }} data-legend-panel-title>legend</text>\n {/* Round 257 / Loop: legend panel header count picks up the\n symmetric 13L/13R inner-padding pattern from the recent-\n signal panel. Pre-R257 the legend header was 13px from\n the left edge (`x=13` title) but only 9px from the right\n edge (`x=215` end-anchored count → 224-215=9), while the\n recent-signal panel header used 13px on BOTH sides (x=13\n title + x=217 end-count → 230-217=13). The two panels\n sit as a side-by-side corner pair — mismatched header\n inner-padding read as a typographic nit on the panel\n chrome. x=211 (= 224-13) restores symmetric 13L/13R so\n the panel pair shares one inset rhythm. The per-row\n count text at x=215 (line ~6321) STAYS at 9px-from-right\n — that one is paired with the flow-arrow swatch geometry\n ('M140,80 Q164,56 196,80') and would visibly tighten\n against the arrow tip if moved further left. Header\n count has no such pairing; it stands alone. */}\n {/* Round 266 / Loop: legend count fill (pal.legendAccent\n — cyber #67e8f9 cyan ↔ light #10b981 emerald) picks up\n theme-toggle transition. Pre-R266 the count snapped\n color on theme flip; R266 eases it alongside the panel\n title (sibling text in the same header band). */}\n {/* Round 292 / Loop: legend panel header count adopts explicit\n fontVariantNumeric: 'tabular-nums' for parity with the\n recent-signal panel header count at line ~5814 (R232).\n The text is already fontFamily='monospace' so digit width\n is technically tabular by definition — the explicit\n directive documents intent at code level, survives a\n future font-family change without silently losing\n tabular alignment, and eliminates an asymmetry between\n two sibling panel-header counts. Sibling treatment to\n R225 (hub digit) / R224 (edge badge) / R232 (chip-row\n counts) — tabular-nums sweep continues wherever digits\n live next to non-digit characters. */}\n {/* Round 310 / Loop: legend panel-header count picks up\n fontWeight=600 for parity with R309 per-row count\n weight. Pre-R310 the header count 'N nodes' rendered\n at default 400 while the per-row counts (working\n 'N' / idle 'N' / offline 'N') went semibold in R309.\n Same hierarchy reason as R309: the count is the DATA\n operators scan; the label ('legend' panel title +\n row labels 'working/idle/offline') is stable\n structural anchor. R309 established the rule at the\n row scope; R310 propagates it up to the panel-\n summary scope so the count typography is consistent\n across both the rollup and per-row counts inside\n the same legend panel. Existing pal.legendAccent\n fill + tabular-nums + R266 fill transition all\n preserved. */}\n {/* Round 336 / Loop: split legend panel count digit from\n unit \" nodes\" with nested opacity-0.7 tspan — sibling\n treatment to the recent-signal panel count and hot-\n count splits above. Three-panel-header surface family\n now sharing the same chip-internal-hierarchy pattern:\n recent flows count + \" flows\" unit at 0.7\n recent hot count + \" hot\" unit at 0.7\n legend nodes count + \" nodes\" unit at 0.7\n data-legend-panel-count-unit on the inner tspan for\n R336 introspection; the parent .textContent still\n reads \"{N} node(s)\" so existing R310 count tests via\n textContent unchanged. */}\n <text\n x=\"211\" y=\"21\" textAnchor=\"end\"\n fill={pal.legendAccent} fontSize=\"10\" fontFamily=\"monospace\" fontWeight=\"600\"\n data-legend-panel-count\n style={{\n transition: 'fill 200ms ease-out',\n fontVariantNumeric: 'tabular-nums',\n }}\n >{sessions.length}<tspan opacity=\"0.7\" data-legend-panel-count-unit> node{sessions.length === 1 ? '' : 's'}</tspan></text>\n {(() => {\n const idleCount = onlineNodes.length - workingCount;\n // R106: rows shift +8 px (was y0=24, 48, 72 → 32, 56, 80)\n // to clear the new header row. R57 panel rect grew 96 →\n // 104 to seat them.\n const rows = [\n /* Round 277 / Loop: legend panel compress 104 → 88 (matches\n recent-signal panel height post-R256) per Vincent\n 5214/5215-5217 simplification ask. Row stride drops\n 24 → 18: row 1 working anchored at y0=32 (unchanged so\n R271 hitbox-swatch-center test at y=21 still passes);\n row 2 idle y0=56→50 (-6); row 3 offline y0=80→68 (-12).\n Flow-arrow swatch path (line ~6607) tracks new offline\n cy from y=80 to y=68. Net: legend panel takes ~15%\n less vertical chrome, panel pair (recent-signal+\n legend) now share same height = symmetric corner\n pair. Tests still pass: R257/R266/R269/R274 probe\n x attrs, fill transitions, text content — none\n sensitive to y0 stride. R271 probes working row\n hitbox y=21 (row.y0-11 with row.y0=32), unchanged.\n Corner-to-center distance increases (panel ends\n higher, further from center) — geometric ring-clear\n improves slightly. */\n /* Round 308 / Loop: continue the R307 legend label 减法.\n 'working node' (12 chars) → 'working' (7 chars):\n the 'node' qualifier is redundant — the row is in\n the LEGEND for a node graph, every row inherently\n describes a node state. 'online idle' (11 chars) →\n 'idle' (4 chars): the 'online' qualifier was\n disambiguation against the offline row, but the\n dashed-vs-solid status ring already discriminates\n online idle from offline visually. After R307+R308\n the three legend labels are all just status words:\n working / idle / offline — a clean 3-state list at\n roughly comparable lengths (7 / 4 / 7) for the\n tightest legend column to date. Pure 减法; visual\n information already encoded by row position +\n status-color swatch + status-ring dashing. */\n { key: 'working' as const, y0: 32, y1: 36, fill: isLight ? '#059669' : '#22c55e', label: 'working', count: workingCount },\n { key: 'idle' as const, y0: 50, y1: 54, fill: isLight ? '#0d9488' : '#2dd4bf', label: 'idle', count: idleCount },\n /* Round 269 / Loop: \" / \" → \" · \" delimiter unification.\n R138 swept the recent-signal row separators from\n ASCII \" / \" to typographic \" · \" (matching filter\n pills, node tooltips, edge badges, active-links\n tooltip). The legend's offline-row label was the\n LAST hardcoded \" / \" holdover in TopoGraph. Same\n monospace cell width (no layout shift), completes\n the R138 delimiter sweep.\n Round 307 / Loop: drop the ' · no SSE' qualifier.\n 'offline · no SSE' (16 chars) → 'offline' (7 chars).\n The visual already communicates the same idea\n redundantly: status ring strokeDasharray='5 5' for\n offline nodes (line ~5193) + gray fill + offline\n row's own gray swatch. Text qualifier was\n technical disambiguation that the visual encodes\n directly. Same R275-R281/R290/R291/R294 减法\n register — remove redundant text the eye doesn't\n need. Sibling row labels 'working node' (12 chars)\n + 'online idle' (11 chars) read at roughly the\n same length now too — legend rows look more\n balanced across the 3 lines. */\n { key: 'offline' as const, y0: 68, y1: 72, fill: isLight ? '#94a3b8' : '#6b7280', label: 'offline', count: offlineNodes.length },\n ];\n return rows;\n })().map(row => {\n // Round 61 / Loop: legend rows pin too — symmetric with the\n // R60 pressure-bar segments. R55 hover stays transient; the\n // new onClick toggles pinnedStatus so users can lock a\n // filter without holding the cursor still. Pinned row gets\n // an inset ring on the swatch (same vocab as R60).\n const isPinned = pinnedStatus === row.key;\n const isRowHovered = hoveredStatus === row.key;\n const isLifted = isRowHovered || isPinned;\n return (\n <g\n key={row.key}\n data-legend-status={row.key}\n data-legend-row-lifted={isLifted ? 'true' : 'false'}\n className=\"anet-topo-svg-focus\"\n role=\"button\"\n tabIndex={0}\n aria-pressed={isPinned}\n // R144: mirror of R143 — extend the row-lift idiom to the\n // legend panel rows for symmetry with the recent-signal\n // panel rows. Same translate-1px transform, same 150ms\n // cubic-bezier timing. R55 hovers the row (transient),\n // R61 pins it (sticky); both states earn the lift.\n // Composes with R105 row-bg-tint and R135 panel hover-\n // shadow → three layers of feedback at three nested\n // scopes (panel chrome → row text lift → bg tint).\n style={{\n cursor: 'pointer',\n transform: isLifted ? 'translateY(-1px)' : undefined,\n transition: 'transform 150ms cubic-bezier(0.4, 0, 0.2, 1)',\n }}\n // R61: stopPropagation on pointerdown so the SVG-level\n // pan handler (R103) doesn't setPointerCapture and\n // redirect the follow-up click away from this <g>.\n // Same trick the node <g> uses (and R52 hub).\n onPointerDown={(e) => e.stopPropagation()}\n onMouseEnter={() => setHoveredStatus(row.key)}\n onMouseLeave={() => setHoveredStatus(prev => prev === row.key ? null : prev)}\n onClick={() => setPinnedStatus(prev => prev === row.key ? null : row.key)}\n >\n {/* R149 / Loop: legend row gains a <title> tooltip\n symmetric with R148's recent-signal row tooltip.\n Same R97 idiom — anywhere the UI shows \"N\" should\n hover-explain WHICH N — applied to the legend\n panel which had been showing only the bucket\n count without alias context. Header line names\n the status bucket; body lists the matched aliases\n with 8-truncate + \"+N more\"; footer hint flips\n with pin state. Hot-lane convention doesn't apply\n here (status buckets aren't traffic counts), so\n no isHot suffix. */}\n <title>{(() => {\n const aliases = row.key === 'working'\n ? onlineNodes.filter(s => s.status === 'working').map(s => s.alias)\n : row.key === 'idle'\n ? onlineNodes.filter(s => s.status !== 'working').map(s => s.alias)\n : offlineNodes.map(s => s.alias);\n const preview = aliases.slice(0, 8).join(', ');\n const suffix = aliases.length > 8 ? ` + ${aliases.length - 8} more` : '';\n return [\n `${row.label} · ${row.count}`,\n aliases.length > 0 ? `${preview}${suffix}` : null,\n isPinned ? 'click to release pin (Esc to clear)' : 'click to pin · hover to preview',\n ].filter(Boolean).join('\\n');\n })()}</title>\n {/* R55 hitbox covers the row so cursor doesn't need to\n be exactly on the 5-px swatch. R105 / Loop: the\n hitbox now also carries a subtle hover/pin tint —\n mirroring R104's recent-signal row treatment so\n both side panels share the list-item idiom. The\n tint borrows the ROW'S OWN swatch colour rather\n than legendAccent, so the user's eye associates\n the tinted row with its colour swatch instead of\n a generic accent. Pin is a stronger tint than\n hover; idle stays fully transparent. */}\n {/* Round 271 / Loop: hitbox y shifts row.y0-12 → row.y0-11\n so hitbox center aligns exactly with swatch cy=row.y0.\n Pre-R271 hitbox spanned y=row.y0-12 to y=row.y0+10\n (center at row.y0-1), with swatch cy at row.y0 — 1px\n asymmetric. Hover/pin tint band drifted 1px above\n the swatch. Post-R271: hitbox spans y=row.y0-11 to\n y=row.y0+11 (center exactly at row.y0), swatch sits\n 11px from both edges (symmetric). Label text\n vertical center also benefits — label baseline\n y=row.y1=row.y0+4, visual midpoint ~row.y0+1.25,\n now ~1.25px below hitbox center (vs ~2.25px pre).\n No height change, no test ripple (other than this\n one), no R260/R268/R270 chrome regressions. */}\n <rect\n x=\"6\" y={row.y0 - 11}\n width=\"170\" height=\"22\" rx=\"3\"\n fill={hoveredStatus === row.key || isPinned ? row.fill : 'transparent'}\n opacity={isPinned ? (isLight ? 0.14 : 0.18)\n : hoveredStatus === row.key ? (isLight ? 0.08 : 0.12)\n : 1}\n data-legend-row-tinted={isPinned ? 'pinned' : hoveredStatus === row.key ? 'hover' : 'none'}\n style={{ transition: 'fill 150ms ease-out, opacity 150ms ease-out' }}\n />\n {/* Round 197 / Loop: swatch dot scales r 5.5 → 7 when its\n row is hovered or pinned. Pre-R197 the swatch was a\n flat constant size — the row got R55/R61 fill brighten,\n R105 bg tint, R144 1px row lift, R181 pin ring, but\n the swatch dot at the row's *visual identity anchor*\n stayed still. Adding the size response gives the\n swatch its own click/hover feel and visually rhymes\n with R177 hub hover-lift (hub ring r 14→17). Stays\n well inside the r=8 R181 pin ring (7 + 0 stroke vs\n 8 - 0.75 inner ≈ 7.25) so the two layers don't fight.\n CSS r-as-property is interpolatable by Chrome/Safari/\n FF post-2020. Geometry-unchanged at rest, so the\n overlap test sees the same baseline. */}\n {/* Round 295 / Loop: legend swatch base radius 5.5 → 6.\n Pre-R295 the swatch idle radius was 5.5 and R197\n grew it to 7 on hover/pin — a 1.5px jump (~27%\n area). Bumping idle to 6 keeps hover at 7, so\n the hover delta becomes a smoother 1px (~36%\n area but 17% radius). Side effect: idle swatch\n reads slightly more like an authored color\n anchor than a faint dot — matches the post-R294\n 减法 register where the legend is one of the\n few remaining persistent canvas-side info\n surfaces. Geometry stays well inside the r=8\n R181 pin ring (6 + 0 stroke vs 8 - 0.75 inner\n ≈ 7.25). data-legend-swatch is unchanged so\n R197 / R55 / R61 tests probe the same handle. */}\n <circle\n cx=\"16\" cy={row.y0}\n r=\"6\"\n fill={row.fill}\n data-legend-swatch={row.key}\n data-legend-swatch-state={isPinned ? 'pinned' : isRowHovered ? 'hover' : 'idle'}\n style={{\n r: isRowHovered || isPinned ? '7px' : '6px',\n transition: 'r 150ms ease-out',\n } as React.CSSProperties}\n />\n {/* R61 pinned-state ring — concentric stroke at r=8 in\n the row colour, draws OUTSIDE the swatch so it\n doesn't fight the fill colour the user is matching.\n Round 181 / Loop: the ring used to mount/unmount\n with the conditional render, snapping on every\n pin/unpin. Now always mounted with opacity gated\n by isPinned + a 150ms transition so the ring\n eases in on pin and out on unpin — same gesture\n vocabulary the R165/R180 smooth-pin-mirror family\n uses for the chip-row pin chips. strokeWidth=1.5\n is the R51 overlap-test sentinel but the test\n selector is gated to g[data-node] ancestors,\n so this legend-internal circle is invisible to\n that probe. pointerEvents:none so the ring can't\n intercept the row click that produced it. */}\n <circle\n cx=\"16\" cy={row.y0} r=\"8\"\n fill=\"none\"\n stroke={row.fill}\n strokeWidth=\"1.5\"\n opacity={isPinned ? 1 : 0}\n data-legend-pin-ring={row.key}\n data-legend-pin-ring-pinned={isPinned ? 'true' : 'false'}\n style={{\n pointerEvents: 'none',\n transition: 'opacity 150ms ease-out',\n }}\n />\n {/* Round 219 / Loop: legend row text gains the same\n letter-spacing pin signature R218 added to group\n labels — 0px → 0.5px when isPinned. Pre-R219 the\n legend row's hover and pin states shared fill\n colour (R55/R61 both brighten to legendHeadline)\n so the text was typographically identical at the\n letter-form level for transient hover vs sticky\n pin. R181 pin ring + R197 swatch grow + R143 row\n lift differentiated the row chrome; R219 adds the\n text-level signature so the LABEL itself reads\n \"locked in\" at type. Mirror of R218's group label\n treatment — chrome-level + type-level pin\n vocabulary now unified across both interactive\n label surfaces (group + legend). transition adds\n `letter-spacing 150ms` alongside R55 fill 150ms;\n same ease pace, same beat. */}\n <text\n x=\"30\" y={row.y1}\n fill={hoveredStatus === row.key || isPinned ? pal.legendHeadline : pal.legendText}\n fontSize=\"11\"\n fontFamily=\"monospace\"\n data-legend-row-label={row.key}\n data-legend-row-label-pinned={isPinned ? 'true' : 'false'}\n style={{\n transition: 'fill 150ms ease-out, letter-spacing 150ms ease-out',\n letterSpacing: isPinned ? '0.5px' : '0px',\n }}\n >{row.label}</text>\n {/* R95: live count anchored to the right edge of the\n panel (x=215, after the flow-arrow swatch). Same\n counts the chip-row shows (\"3 working\" etc.) but\n here next to the swatch the user is matching —\n saves crossing the canvas to the chip row for\n the number. text-anchor=end aligns the column\n visually like a table. pointerEvents:none so the\n count doesn't intercept the row hover hitbox.\n\n Round 204 / Loop: count text recedes when the\n tier is empty. Pre-R204 the \"0\" sat at the same\n opacity 0.65 as \"12\" — visually identical, so\n the eye got zero signal that a status tier was\n empty unless the operator read the digit. R204\n drops empty rows to 0.30 (dark) / 0.28 (light)\n so empty tiers fade into the panel chrome while\n populated tiers stay visually prominent. R204 a\n crossing zero / coming back from zero eases via\n the existing 150ms opacity transition. data-\n legend-count-empty exposes the binary signal\n for tests. */}\n {/* Round 239 / Loop: legend count text gains a tier-\n coloured fill on hover/pin, completing the hover-\n deepen-own-hue idiom at this surface. Pre-R239 the\n count digit's opacity bumped 0.65→0.95 on hover\n (R204 thinning) but its fill stayed at the neutral\n pal.legendText gray — same digit, brighter gray,\n no tier identity. R239 flips fill to row.fill\n (green/teal/slate per tier) when the row is\n hovered OR pinned, so the count lights up in its\n OWN colour, matching the swatch directly above it.\n The whole row now reads as one tier-coloured unit\n under cursor (swatch + label + count); R55/R197\n already do this for swatch + label, R239 closes\n the trio at the count. Opacity transition stays at\n 150ms; fill joins the same transition list at 150ms\n so the colour shift eases alongside the opacity\n ramp. data-legend-count-fill exposes the active\n fill state for tests; empty tiers (row.count===0)\n stay at pal.legendText regardless — empty doesn't\n get to claim tier identity. 8th surface in the\n hover-deepen-own-hue family. */}\n {/* Round 274 / Loop: legend per-row count picks up\n tabular-nums (sibling treatment to R225's recent-\n signal panel header flow-count + R230's group-\n label pip strip). The text uses fontFamily=\n 'monospace' which is typically tabular by\n nature, but some monospace implementations have\n subtle digit-pair width variance (e.g., '0' vs\n '1' at the visual boundary). Explicit\n fontVariantNumeric: 'tabular-nums' is belt-and-\n suspenders: locks digit widths regardless of\n the rendered monospace font, so the count\n column stays planted as offline/idle/working\n counters roll across 9→10 / 99→100 thresholds.\n Pure CSS-level addition, no layout shift.\n 10th surface in the info-density tabular-nums\n sweep family. */}\n {/* Round 309 / Loop: legend per-row count gains\n fontWeight=600 (semibold). The count is the\n DATA the operator scans (how many working /\n idle / offline nodes); the row label is the\n stable status word. Default weight (400) on\n both makes them visually equal — but a glance-\n first read pattern needs the digit to register\n faster than the label.\n fontWeight=600 gives the digit semibold\n emphasis (matching the h2 'Command mesh'\n semibold in the title block) while the row\n label stays at default 400/normal — a clean\n 'digit semibold > label regular' hierarchy\n that makes the count the optical anchor in\n each row. After R307+R308 simplified the\n labels to single status words, the count\n becomes proportionally more important; this\n round emphasizes that role typographically. */}\n <text\n x=\"215\" y={row.y1}\n textAnchor=\"end\"\n fill={row.count > 0 && (hoveredStatus === row.key || isPinned) ? row.fill : pal.legendText}\n fontSize=\"11\"\n fontFamily=\"monospace\"\n fontWeight=\"600\"\n opacity={row.count === 0\n ? (isLight ? 0.28 : 0.30)\n : (hoveredStatus === row.key || isPinned ? 0.95 : 0.65)}\n data-legend-count={row.key}\n data-legend-count-empty={row.count === 0 ? 'true' : 'false'}\n data-legend-count-fill={row.count > 0 && (hoveredStatus === row.key || isPinned) ? 'tier' : 'neutral'}\n style={{ pointerEvents: 'none', transition: 'opacity 150ms ease-out, fill 150ms ease-out', fontVariantNumeric: 'tabular-nums' }}\n >{row.count}</text>\n </g>\n );\n })}\n {/* Flow-arrow swatch tracks the offline row — R106 shifted\n rows down by 8 px to make space for the panel header so\n this moves from y=72 to y=80. Drop its pointerEvents so\n the offline legend row stays hoverable (R55). It's\n decoration, no need to receive events. */}\n {/* Round 254 / Loop: legend flow-arrow swatch stroke\n transition for theme toggle (cyber #67e8f9 ↔ light\n #10b981). Last theme-driven legend element snap. */}\n {/* Round 277 / Loop: flow-arrow path tracks new offline-row\n cy=68 after the legend panel compress (was 80 pre-R277).\n Endpoints follow the offline row to keep the swatch\n logically tied to the row it demonstrates; control point\n proportionally shifts so apex stays mid-arc between\n rows 2 and 3. */}\n <path d=\"M140,68 Q164,44 196,68\" fill=\"none\" stroke={pal.flowEdge} strokeWidth=\"3\" markerEnd=\"url(#topo-arrow)\" data-legend-flow-arrow style={{ pointerEvents: 'none', transition: 'stroke 200ms ease-out' }} />\n </g>\n\n {/* Round 282 / Loop: sleep2agi brand watermark per Vincent\n 5215 ask (relayed via 通信龙). Plain monospace text at\n canvas bottom-left (the only fully-empty corner — top\n corners hold recent-signal + legend panels, bottom-\n right holds the chrome strip). No icon yet — public/\n has only favicon.svg (small abstract network icon\n with hardcoded #0a0a1a dark bg that wouldn't blend on\n light theme) + intern_avatar.png (书生 brand-specific).\n Without a sleep2agi-specific crescent/lockup asset,\n R282 ships a low-opacity text-only mark; R283+ can\n swap in the real logo if Vincent provides the asset.\n\n Position: x=16 (matches the 16-unit SVG inset that the\n corner panels use); y=672 (≈12 px from viewBox bottom\n y=680, descender ≈ y=675, so the entire glyph sits\n clear of the bottom edge). Theme-aware fill:\n pal.legendText (cyber #94a3b8 slate-400 ↔ light\n #475569 slate-600). 0.4 opacity makes it a\n watermark — present but not visually loud. Pointer-\n events:none so it can't intercept clicks on the\n canvas backdrop.\n\n Note: the brand mark is INTENTIONALLY in a corner\n that no overlay/panel occupies, AND it's purely\n decorative additive after 7 rounds of 减法 (R275-\n R281). Adds 1 small text element back into the\n canvas — but Vincent specifically asked for it. */}\n {/* Round 289 / Loop: brand watermark picks up letterSpacing\n 0.5px. For a 9-character wordmark at fontSize 11 monospace,\n 0.5px between characters (8 gaps × 0.5 = 4px total\n widening) lifts \"sleep2agi\" from \"body text that happens\n to be a name\" to \"deliberate wordmark register\". Same\n R285-family idiom (kicker tracking-widest, title\n tracking-tight) applied to the brand mark — letter-\n spacing as typographic intent. Stays well inside the\n bottom-left corner; opacity 0.4 unchanged so the\n watermark stays a watermark. */}\n <text\n x=\"16\" y=\"672\"\n fontSize=\"11\" fontFamily=\"monospace\" fontWeight=\"600\"\n letterSpacing=\"0.5\"\n fill={pal.legendText}\n opacity=\"0.4\"\n data-topo-brand-watermark\n style={{ pointerEvents: 'none', transition: 'fill 200ms ease-out' }}\n >sleep2agi</text>\n {/* v0.10.0 Hero 3 Wave 1 / RFC §3.I (Vincent 5215 + 通信龙\n lead-autonomy Q4 dual-anchor minimal): canvas top-left\n crescent moon brand mark, visible ONLY when the\n recent-signal panel is hidden (composes with §3.C). The\n two never co-exist — when flowLinks.length > 0 the\n recent-signal panel occupies the (16,16) corner; when\n flowLinks.length === 0 the corner is empty and the\n brand crescent fills it. R310 title-block crescent\n remains the primary mark; this one is the secondary\n canvas-internal anchor (Q4 dual-anchor minimal).\n Inline path geometry identical to public/sleep2agi-\n logo.svg + the title-block SVG (mask = outer disc minus\n offset inner disc → crescent). Local mask id\n (`s2a-canvas-corner-mask`) prevents collision with the\n other inline crescents. opacity 0.35 (slightly more\n subtle than the bottom watermark's 0.4 since the\n canvas top-left has more contrast headroom). */}\n {/* Round 327 / Loop: canvas brand crescent joins the always-\n mount-opacity-gate family (R181/R182/R183/R213×2/R214/R215/\n R221/R222/R223). Pre-R327 the crescent conditionally\n mounted on `flowLinks.length === 0` — first flow arriving\n SNAP-removed it, last flow leaving SNAP-added it. The\n recent-signal panel at the same (16,16) corner has the\n same snap problem on its conditional-mount path; this\n round closes the crescent's snap-on-mount (the panel's\n own crossfade is a larger surface, deferred).\n\n Always-mounted with `opacity={flowLinks.length === 0 ?\n 0.35 : 0}` + 300ms ease-out transition: when the panel\n hides, the crescent fades in over 300ms; when the panel\n shows, the crescent fades out. Same opacity ramp time\n the R175 panel-fade-in uses for cascade rhythm. data-\n topo-brand-canvas-mark-visible exposes the gate for\n tests. */}\n <g\n opacity={flowLinks.length === 0 ? 0.35 : 0}\n data-topo-brand-canvas-mark\n data-topo-brand-canvas-mark-visible={flowLinks.length === 0 ? 'true' : 'false'}\n style={{ pointerEvents: 'none', transition: 'opacity 300ms ease-out, fill 200ms ease-out' }}\n >\n <defs>\n <mask id=\"s2a-canvas-corner-mask\">\n <rect x=\"0\" y=\"0\" width=\"28\" height=\"28\" fill=\"black\" />\n <circle cx=\"14\" cy=\"14\" r=\"12\" fill=\"white\" />\n <circle cx=\"17.5\" cy=\"13\" r=\"10\" fill=\"black\" />\n </mask>\n </defs>\n <rect\n x=\"16\" y=\"16\" width=\"28\" height=\"28\"\n fill={pal.legendText}\n mask=\"url(#s2a-canvas-corner-mask)\"\n />\n </g>\n </svg>\n\n {/* Round 30 / Loop: minimap. Big fleets in fullscreen mode at high\n zoom let users lose their position — the minimap shows the\n whole topology miniaturised plus a viewport rectangle so the\n user always knows where they are. Click anywhere to recenter\n the canvas there. Only mounted when the view is non-default\n (zoomed or panned) since at 1× centered the minimap and the\n canvas show the same thing. HTML overlay so it stays fixed\n while the SVG transforms. */}\n {(() => {\n const isDefaultView = Math.abs(view.zoom - 1) < 0.01 && Math.abs(view.x) < 1 && Math.abs(view.y) < 1;\n if (isDefaultView || (onlineNodes.length + offlineNodes.length) === 0) return null;\n const MW = 120, MH = 82;\n const sx = MW / VIEWBOX_W, sy = MH / VIEWBOX_H;\n const rectX = (-view.x / view.zoom) * sx;\n const rectY = (-view.y / view.zoom) * sy;\n const rectW = (VIEWBOX_W / view.zoom) * sx;\n const rectH = (VIEWBOX_H / view.zoom) * sy;\n return (\n <div\n /* Round 332 / Loop: minimap container rounded-md → rounded-lg\n (6 → 8 px) — continues the R330-R331 corner-radius cascade\n onto the minimap overlay card. The minimap is a smaller\n surface than the inner SVG panels (120×82 vs 230×88), so\n it sits one tier inward in the size hierarchy: panels at\n rx=10 (R331), minimap at rounded-lg=8 (R332), inner\n detail card at rx=8 (codex 8f981a9). Same 2 px gradient\n step the rest of the cascade uses. Geometry-safe — the\n minimap is an HTML overlay positioned `bottom: 56` +\n `right-4`, no impact on SVG layout or topo-overlap-test. */\n className=\"absolute right-4 rounded-lg border shadow-lg shadow-black/30 overflow-hidden anet-fade-in anet-topo-chip-focus\"\n /* Round 254 / Loop: minimap container theme transitions —\n background-color, border-color, color (used for SVG\n currentColor inside) all ease at 200ms alongside the\n R254 wrapper + R247 panel treatments. */\n style={{ bottom: 56, background: pal.legendBox.fill, borderColor: pal.containerBorder, cursor: 'crosshair', color: pal.legendAccent, transition: 'background-color 200ms ease-out, border-color 200ms ease-out, color 200ms ease-out' }}\n // R157: minimap a11y completion. Pre-R157 the element had\n // role=\"img\" + aria-label but no tabIndex / onKeyDown — it\n // was clickable for mouse users (recenter to where you\n // clicked) but tab-unreachable. role=\"img\" was also wrong\n // for an interactive surface; role=\"button\" matches the\n // canonical pattern R116 / R139 / R140 / R151 / R152 use.\n // Keyboard activation can't compute a click position, so\n // Enter / Space falls back to resetView() — same gesture\n // as the dedicated reset button (R104). Click semantics\n // unchanged; only added a clarifying tail to the aria-\n // label + title. anet-topo-chip-focus picks up R155's\n // cyan outline via color: pal.legendAccent inline so the\n // currentColor inherits cleanly on the rounded card.\n role=\"button\"\n tabIndex={0}\n aria-label=\"Topology minimap — click to recenter, Enter to reset view\"\n title=\"Minimap · click to recenter · Enter to reset view\"\n onClick={(e) => {\n const r = e.currentTarget.getBoundingClientRect();\n const fx = (e.clientX - r.left) / r.width;\n const fy = (e.clientY - r.top) / r.height;\n setView(prev => ({\n ...prev,\n x: VIEWBOX_W / 2 - fx * VIEWBOX_W * prev.zoom,\n y: VIEWBOX_H / 2 - fy * VIEWBOX_H * prev.zoom,\n }));\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n resetView();\n }\n }}\n // R346: viewport rect hover affordance driven by parent.\n onMouseEnter={() => setHoveredMinimap(true)}\n onMouseLeave={() => setHoveredMinimap(false)}\n onFocus={() => setHoveredMinimap(true)}\n onBlur={() => setHoveredMinimap(false)}\n data-topo-minimap\n data-topo-minimap-hovered={hoveredMinimap ? 'true' : 'false'}\n >\n <svg width={MW} height={MH} viewBox={`0 0 ${MW} ${MH}`} style={{ display: 'block' }}>\n {/* Round 198 / Loop: minimap dots gain smooth status\n transitions. Pre-R198 a session flipping working→idle\n or going offline made the minimap dot snap in a single\n frame (opacity 0.9→0.5, r 1.7→1.2, fill swap). The\n canvas itself eases all of these via R167 status-ring\n transitions, R10 freshness, R3 fade-in — but the\n minimap mirror was still snap-cut. Adding opacity /\n fill / r to the CSS transition list lets a status\n change ripple smoothly through both views at the\n same rhythm. 200ms matches the R167 nodeStrokeWidth\n interpolation on the main canvas so the two surfaces\n visually flip in sync. r-as-property is well supported\n Chrome ≥ 95 / Safari ≥ 16 / FF ≥ 70 (same support\n matrix R197 just leveraged on the legend swatch).\n data-topo-minimap-dot exposes each dot for the test;\n data-topo-minimap-dot-online encodes the binary status\n used by the visible attributes. */}\n {[...onlineNodes, ...offlineNodes].map(s => {\n const p = nodePositions[s.alias];\n if (!p) return null;\n const sseN = (s.network_id ? sseSessions[`${s.network_id}:${s.alias}`] : undefined) ?? sseSessions[s.alias];\n const isOn = s.status !== 'offline' || !!sseN;\n const st = nodeStatus(s, isOn, isLight);\n return (\n <circle\n key={s.alias}\n cx={p.x * sx} cy={p.y * sy}\n r={isOn ? 1.7 : 1.2}\n fill={st.primary}\n opacity={isOn ? 0.9 : 0.5}\n data-topo-minimap-dot={s.alias}\n data-topo-minimap-dot-online={isOn ? 'true' : 'false'}\n style={{\n transition: 'opacity 200ms ease-out, fill 200ms ease-out, r 200ms ease-out',\n } as React.CSSProperties}\n />\n );\n })}\n {/* viewport rectangle.\n Round 199 / Loop: rect dimensions transition smoothly\n during R169 smoothView arming (discrete zoom button\n clicks + keyboard +/− + reset/fit). Pre-R199 the main\n canvas crossfaded over R168's 280ms opacity blend\n while the minimap viewport rect snap-cut to its new\n x/y/w/h in one frame — exactly the same rhythm\n mismatch R198 just closed for the minimap dots, now\n fixed for the rectangle that frames them.\n\n Gated to smoothView=true so continuous wheel-zoom /\n drag-pan stay snappy (a CSS transition during drag\n would cause the rect to chase the cursor with a\n 280ms lag). Discrete zooms arm smoothView for 350ms,\n long enough to cover the 280ms x/y/w/h transition.\n\n Setting x/y/width/height as CSS PROPERTIES (style.x\n etc.) — same approach R197 used for legend swatch\n r. Modern Chrome/Safari/FF interpolate these.\n\n data-topo-minimap-viewport / -smooth expose state for\n tests. */}\n {/* Round 287 / Loop: minimap viewport rect strokeWidth\n 1 → 1.5. The rect frames the user's current view\n within the full topology — it IS the wayfinding\n indicator. At 1px stroke against a 120×82 mini-\n canvas it was readable but reserved; 1.5px gives\n the boundary clearer presence without crowding the\n miniaturised dots (still r 1.2-1.7) inside.\n Same micro-polish family as R283 monogram stroke\n 1 → 1.5 — small visual-weight bump on a high-\n information element to lift it above ambient\n chrome. opacity 0.9 stays — strokeWidth alone\n does the lifting. */}\n <rect\n x={Math.max(0, rectX)} y={Math.max(0, rectY)}\n width={Math.max(0, Math.min(MW - Math.max(0, rectX), rectW))}\n height={Math.max(0, Math.min(MH - Math.max(0, rectY), rectH))}\n fill=\"none\" stroke={pal.legendAccent}\n // R346: strokeWidth + opacity tween on container hover.\n strokeWidth={hoveredMinimap ? '1.75' : '1.5'}\n opacity={hoveredMinimap ? '1' : '0.9'}\n data-topo-minimap-viewport\n data-topo-minimap-viewport-smooth={smoothView ? 'true' : 'false'}\n data-topo-minimap-viewport-hover={hoveredMinimap ? 'true' : 'false'}\n style={{\n transition: smoothView\n ? 'x 280ms ease-out, y 280ms ease-out, width 280ms ease-out, height 280ms ease-out, stroke-width 200ms ease-out, opacity 200ms ease-out'\n : 'stroke-width 200ms ease-out, opacity 200ms ease-out',\n } as React.CSSProperties}\n />\n </svg>\n </div>\n );\n })()}\n {/* Round 103 (issue #81): zoom / pan / fullscreen controls — HTML\n overlay so they stay fixed while the SVG content transforms.\n Round 104: Vincent 实测 — the reset action used to be hidden\n behind the \"%\" label (looked like an indicator, not a button).\n Split into a plain % readout + an explicit reset button with\n its own icon + tooltip. */}\n {/* Round 261 / Loop: chrome strip bottom-3 right-3 (12 CSS px) →\n bottom-4 right-4 (16 CSS px) to align HTML overlay padding\n with the SVG corner panels at (16, 16) panel-translate. Pre-\n R261 the SVG panels (at 16 SVG units from canvas edges,\n ≈ 15 CSS px after render-scale ~0.94) and the HTML chrome\n (at 12 CSS px) sat at visually different distances from\n the canvas edges — small but real ~3 CSS px optical\n asymmetry between SVG-layer and HTML-layer overlay padding.\n 16 CSS px ≈ 17 SVG units, unifying the visual padding\n vocabulary across both layers. Sibling change at the\n minimap container (line ~6444, `absolute right-3` →\n `right-4`) keeps the bottom-right corner HTML overlays\n aligned at the same canvas-edge inset. */}\n {/* Round 326 / Loop: chrome strip outer wrapper gap 1.5 → 2\n (6px → 8px between control groups). Pre-R326 the four\n chrome groups (nodeSize segmented S/M/L, zoom +/100%/−,\n reset, fullscreen) sat 6px apart — close enough that on a\n busy canvas with bright cyan accents they read as one\n uniform strip rather than four distinct affordances. Bump\n to 8px gives each group its own visual breath without\n disturbing the bottom-4 right-4 corner-inset alignment.\n Sibling treatment to R298/R299 title-block gap polish on\n the top side of the canvas — both ends of the canvas\n chrome now breathe at the same 8px rhythm. Geometry-safe\n for the overlap-test (chrome is HTML overlay on top of\n the SVG, not part of the viewBox 1000x680 surface; ring\n r=325 / grid gx0 layout untouched). */}\n <div className=\"absolute bottom-4 right-4 flex items-center gap-2 text-xs select-none\" data-topo-chrome>\n {/* #113: node size — S / M / L segmented control (Vincent 4727).\n R154: stable data-* hooks for tests + focus-visible ring so\n keyboard navigation lands somewhere visible against the\n dark canvas (browser default outline often vanishes on\n cyber theme). */}\n {/* Round 264 / Loop: nodeSize wrapper gains theme-toggle\n transition. Pre-R264 the wrapper's bg (pal.legendBox.fill)\n + borderColor (pal.containerBorder) were inline theme-\n conditional, but neither inline transition nor a\n transition-colors className → wrapper snapped on cyber↔\n light flip while the inner S/M/L buttons eased via their\n own transition-colors. Same R254 holdover pattern that\n R263 just closed at the canvas wrapper scope, now at the\n chrome strip's nodeSize sub-wrapper scope. */}\n <div\n className=\"flex items-center rounded-md border overflow-hidden\"\n style={{\n background: pal.legendBox.fill,\n borderColor: pal.containerBorder,\n transition: 'background-color 200ms ease-out, border-color 200ms ease-out',\n }}\n role=\"group\"\n aria-label=\"Node size\"\n data-topo-chrome-fleet-group-trailer\n >\n {([['S', 0.7], ['M', 0.84], ['L', 1]] as const).map(([lbl, v], idx) => {\n const popKey = `size-${lbl}` as 'size-S' | 'size-M' | 'size-L';\n return (\n <button\n key={lbl}\n onClick={() => { popChrome(popKey); pickNodeScale(v); }}\n aria-pressed={nodeScale === v}\n data-topo-chrome-nodesize={lbl}\n data-topo-chrome-nodesize-active={nodeScale === v ? 'true' : 'false'}\n data-topo-chrome-nodesize-popping={chromePopping === popKey ? 'true' : 'false'}\n title={`Node size: ${lbl === 'S' ? 'small' : lbl === 'M' ? 'medium' : 'large'}`}\n // Round 179 / Loop: nodeSize S/M/L active-button hover\n // variant closes the inconsistency with R163 layout\n // toggle and R178 fullscreen. Pre-R179 the active\n // (selected) S/M/L button had bg-cyan-500/15 + text-\n // cyan-300 but NO hover response — the chip looked\n // 'locked', not 'still interactive'. R163 and R178\n // both add hover:bg-cyan-500/20 on the active variant\n // so the active chip stays responsive to mouse. R179\n // closes the trio so all three chrome active-cyan\n // surfaces ship the same gesture.\n // Round 196 / Loop: nodeSize buttons pick up press-state\n // (active:) — selected variant deepens to cyan-500/25,\n // unselected to white/10. Same tier pattern as R196 layout\n // toggle + zoom/reset/fullscreen below.\n // Round 250 / Loop: nodeSize buttons close the chrome-pop\n // family — every clickable chrome button now fires the\n // R186 .anet-chrome-pop scale-pulse on release. R171\n // canvas crossfade (nodeSizeSwitching) keeps masking the\n // node radius change at the global scope; R250 chrome-pop\n // adds the LOCAL button-level confirmation. The two\n // happen simultaneously without conflict — local scale\n // pulse on the button, global canvas dim around it.\n // Milestone round: the entire chrome strip (zoom -/+,\n // ring/grid, fullscreen, S/M/L) now speaks one\n // consistent click vocabulary.\n /* Round 270 / Loop: nodeSize INACTIVE buttons align with\n the Layout toggle's R163 hover-preview pattern. Pre-\n R270 inactive S/M/L used `hover:bg-white/5\n active:bg-white/10` (neutral white tint) while the\n Layout toggle's inactive Ring/Grid uses `hover:bg-\n cyan-500/5 active:bg-cyan-500/15` (faint cyan ghost\n that previews what the active state will look like —\n the active variant is bg-cyan-500/15). Two different\n hover vocabularies for visually-analogous toggle\n controls. R270 unifies inactive toggle hover to\n cyan so all TOGGLE chrome buttons (Layout / nodeSize\n / fullscreen) preview their active state on hover.\n Pure actions (zoom -/+, reset) stay white — they\n aren't toggles, have no active state to preview. */\n className={`px-2 py-1 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset ${idx > 0 ? 'border-l' : ''} ${nodeScale === v ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25' : 'hover:bg-cyan-500/5 active:bg-cyan-500/15'}${chromePopping === popKey ? ' anet-chrome-pop' : ''}`}\n style={{ color: nodeScale === v ? undefined : pal.legendText, borderColor: pal.containerBorder }}\n >\n {lbl}\n </button>\n );\n })}\n </div>\n {/* Round 255 / Loop: semantic gap between the fleet-control group\n (node size S/M/L) and the view-control group (zoom / reset /\n fullscreen). Pre-R255 the four groups sat at uniform gap-1.5\n (6px); the spatial signal read as \"4 separate things\" instead\n of \"1 fleet control + 3 view controls\". Doubling the gap before\n the first view-control (ml-1.5 = 6px stacks on top of the\n parent's gap-1.5 = 6px, total 12px) communicates the semantic\n boundary through proximity alone — classic \"law of proximity\"\n layout polish, no extra chrome, no new visual elements.\n data-topo-chrome-view-group-leader marks the boundary surface\n for the test probe; data-topo-chrome-fleet-group-trailer marks\n the nodeSize wrapper's right edge for the gap measurement. */}\n <div\n className=\"ml-1.5 flex items-center rounded-md border overflow-hidden\"\n style={{\n background: pal.legendBox.fill,\n borderColor: pal.containerBorder,\n transition: 'background-color 200ms ease-out, border-color 200ms ease-out',\n }}\n data-topo-chrome-view-group-leader\n data-topo-chrome-zoom-wrapper\n >\n <button\n onClick={() => { popChrome('zoom-out'); zoomByDiscrete(1 / 1.2); }}\n data-topo-chrome-zoom-out\n data-topo-chrome-zoom-out-popping={chromePopping === 'zoom-out' ? 'true' : 'false'}\n // R196: press-state deepens bg one tier above hover (white/5\n // → white/10) so mouse-down has a tactile dim before the\n // R186 icon pop fires on release.\n className=\"px-2 py-1 hover:bg-white/5 active:bg-white/10 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset\"\n style={{ color: pal.legendText }}\n aria-label=\"Zoom out\"\n title=\"Zoom out (−)\"\n >\n {/* R186: icon pop on click. CSS animation runs once;\n React removes the class after 240ms so a quick\n re-click can replay. */}\n <svg\n width=\"12\" height=\"12\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\"\n aria-hidden\n className={chromePopping === 'zoom-out' ? 'anet-chrome-pop' : undefined}\n data-topo-chrome-zoom-out-icon\n ><path d=\"M5 12h14\" /></svg>\n </button>\n {/* Round 192 / Loop: zoom-level readout span participates in the\n R186 click-feel pop alongside the +/− icons. Pre-R192 a click\n on + or − triggered:\n · icon pop (R186, ~220ms scale 1→1.06→1)\n · canvas crossfade (R169, ~280ms opacity blend)\n · readout text snap (instant — 100% → 120%)\n The readout was the only surface that didn't acknowledge the\n gesture. Reusing the existing .anet-chrome-pop CSS keyframe\n (no new keyframes) lets the \"%\" number gently bounce in\n sync with the icon — same 0.22s ease-out, transform-origin\n center. transform-box: fill-box on the keyframe is\n SVG-specific and harmlessly ignored on this HTML span. The\n base layout classes (px / border / tabular-nums / minWidth)\n stay intact; only when chromePopping is 'zoom-in' or\n 'zoom-out' do we splice in the animation class. Same\n React-clears-after-240ms cleanup R186 already runs, so the\n class can replay on a repeat click. */}\n {/* Round 312 / Loop: chrome strip zoom readout '{N}%'\n picks up `font-medium` (500). Extends the R309-R311\n 'data digit weighs more than label' rule to the\n chrome strip's one data display — the zoom\n percentage. Every other chrome strip text is a\n control (S/M/L buttons, zoom +/-, reset, fullscreen,\n Ring/Grid labels); the percent readout is the only\n live DATA. font-medium (not 600 like the SVG panel\n counts) is a tier below because the readout sits in\n HTML chrome context (lighter visual baseline) where\n 500 reads as 'noticeably data-prominent' without\n competing with the SVG panel counts. tabular-nums\n + minWidth 46 stay (R225 family), the existing R264\n color/border transitions stay, the R186 chrome-pop\n class still toggles on zoom-click. */}\n <span\n className={`px-2 py-1 tabular-nums font-medium border-x text-center${\n chromePopping === 'zoom-in' || chromePopping === 'zoom-out'\n ? ' anet-chrome-pop' : ''\n }`}\n data-topo-chrome-zoom-level\n data-topo-chrome-zoom-level-popping={\n chromePopping === 'zoom-in' || chromePopping === 'zoom-out'\n ? 'true' : 'false'\n }\n data-topo-chrome-zoom-level-hover={hoveredZoomLevel ? 'true' : 'false'}\n onMouseEnter={() => setHoveredZoomLevel(true)}\n onMouseLeave={() => setHoveredZoomLevel(false)}\n style={{\n color: pal.legendText,\n borderColor: pal.containerBorder,\n minWidth: 46,\n display: 'inline-block',\n // R347: letter-spacing hover tween — extends R344/R345\n // hover-letter-spacing family into the chrome strip.\n letterSpacing: hoveredZoomLevel ? '0.5px' : '0',\n /* Round 264 / Loop: zoom level readout gains theme-toggle\n transition. The span has theme-driven color (pal.\n legendText) + border-x (pal.containerBorder via the\n inline borderColor) but className lacks transition-\n colors — the readout's text + side dividers snapped\n on theme flip while siblings eased. Sibling treatment\n to the nodeSize + zoom wrapper transitions added this\n round. */\n transition: 'color 200ms ease-out, border-color 200ms ease-out, letter-spacing 200ms ease-out',\n }}\n title=\"Current zoom level\"\n >\n {Math.round(view.zoom * 100)}%\n </span>\n <button\n onClick={() => { popChrome('zoom-in'); zoomByDiscrete(1.2); }}\n data-topo-chrome-zoom-in\n data-topo-chrome-zoom-in-popping={chromePopping === 'zoom-in' ? 'true' : 'false'}\n // R196: press-state (mirror of zoom-out above).\n className=\"px-2 py-1 hover:bg-white/5 active:bg-white/10 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 focus-visible:ring-inset\"\n style={{ color: pal.legendText }}\n aria-label=\"Zoom in\"\n title=\"Zoom in (+)\"\n >\n {/* R186: icon pop on click. Same one-shot CSS animation\n as zoom-out; React removes the class after 240ms. */}\n <svg\n width=\"12\" height=\"12\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\"\n aria-hidden\n className={chromePopping === 'zoom-in' ? 'anet-chrome-pop' : undefined}\n data-topo-chrome-zoom-in-icon\n ><path d=\"M12 5v14M5 12h14\" /></svg>\n </button>\n </div>\n <button\n onClick={() => { armResetSpin(); resetView(); }}\n data-topo-chrome-reset\n data-topo-chrome-reset-spinning={resetSpinning ? 'true' : 'false'}\n // R196: press-state deepens before R184 reset-spin fires on\n // release — mouse-down dim then 450ms spin = full handshake.\n className=\"p-1.5 rounded-md border hover:bg-white/5 active:bg-white/10 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60\"\n style={{ background: pal.legendBox.fill, borderColor: pal.containerBorder, color: pal.legendText }}\n aria-label=\"Reset view\"\n title=\"Reset zoom + pan (0, or double-click the canvas)\"\n >\n {/* R184: the refresh-arrow icon does one counter-clockwise\n rotation on click. CSS animation runs once; React removes\n the className after 460ms (just past the 450ms duration)\n so a subsequent click can replay. */}\n {/* Round 288 / Loop: reset icon strokeWidth 2 → 2.5 unifies\n the chrome icon weight family. Pre-R288 zoom-in / zoom-\n out icons rendered at strokeWidth 2.5 while reset +\n fullscreen icons sat thinner at strokeWidth 2 — five\n chrome icons in a single horizontal strip with two\n weights is exactly the inconsistency R268 closed for\n border colors. Same unification idiom now applied to\n icon strokes: zoom (2.5) + reset (2.5) + fullscreen\n (2.5) all share one weight. View-box (24×24) and\n display size (13×13) unchanged, so geometry stays\n pixel-stable — only the stroke deepens. */}\n <svg\n width=\"13\" height=\"13\" viewBox=\"0 0 24 24\"\n fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\"\n strokeLinecap=\"round\" strokeLinejoin=\"round\"\n aria-hidden\n className={resetSpinning ? 'anet-reset-spin' : undefined}\n data-topo-chrome-reset-icon\n >\n <path d=\"M3 12a9 9 0 1 0 9-9 9 9 0 0 0-6.4 2.6L3 8\" />\n <path d=\"M3 3v5h5\" />\n </svg>\n </button>\n {/* Round 178 / Loop: fullscreen chrome button picks up the\n active-state visual indicator R163 introduced for the\n Ring/Grid layout toggle. Pre-R178 the button changed\n icon when isFullscreen flipped but its background +\n foreground stayed unchanged — operators in fullscreen\n didn't get a strong 'you're in fullscreen' cue. Adding\n the bg-cyan-500/15 + text-cyan-300 active variant\n mirrors R163's pattern; hover variants tier 1\n brighter (cyan-500/20) when active so the chip\n continues to respond to mouse. Inline style now omits\n background + color when active so the Tailwind cyan\n classes win specificity. */}\n <button\n onClick={() => { popChrome('fullscreen'); toggleFullscreen(); }}\n data-topo-chrome-fullscreen\n data-topo-chrome-fullscreen-active={isFullscreen ? 'true' : 'false'}\n data-topo-chrome-fullscreen-popping={chromePopping === 'fullscreen' ? 'true' : 'false'}\n // R196: fullscreen also picks up press-state — active variant\n // deepens cyan-500/20 → cyan-500/25 on press; non-active\n // deepens white/5 → white/10.\n // R249: chrome-pop on click — same one-vocabulary click signal\n // as layout toggle and zoom buttons.\n /* Round 270 / Loop: fullscreen INACTIVE picks up the cyan\n hover-preview pattern from the Layout toggle. The\n fullscreen button is a TOGGLE (enter/exit fullscreen) so\n its inactive state benefits from the same \"hover previews\n active state\" idiom R163 designed. Sibling treatment to\n the nodeSize buttons at line ~6711. */\n className={`p-1.5 rounded-md border transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-cyan-400/60 ${\n isFullscreen\n ? 'bg-cyan-500/15 text-cyan-300 font-medium hover:bg-cyan-500/20 active:bg-cyan-500/25'\n : 'hover:bg-cyan-500/5 active:bg-cyan-500/15'\n }${chromePopping === 'fullscreen' ? ' anet-chrome-pop' : ''}`}\n style={{\n borderColor: pal.containerBorder,\n ...(isFullscreen\n ? {}\n : { background: pal.legendBox.fill, color: pal.legendText }),\n }}\n aria-label={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}\n title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}\n >\n {/* R288 / Loop: fullscreen enter + exit icons strokeWidth\n 2 → 2.5 — same chrome-icon weight unification described\n at the reset icon above. data-topo-chrome-fullscreen-\n icon attribute exposes BOTH variants (entered / exited)\n for the round's stroke-width regression probe. */}\n {isFullscreen ? (\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden data-topo-chrome-fullscreen-icon=\"exit\">\n <path d=\"M8 3v4a1 1 0 0 1-1 1H3M21 8h-4a1 1 0 0 1-1-1V3M3 16h4a1 1 0 0 1 1 1v4M16 21v-4a1 1 0 0 1 1-1h4\" />\n </svg>\n ) : (\n <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2.5\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden data-topo-chrome-fullscreen-icon=\"enter\">\n <path d=\"M3 8V5a2 2 0 0 1 2-2h3M21 8V5a2 2 0 0 0-2-2h-3M3 16v3a2 2 0 0 0 2 2h3M21 16v3a2 2 0 0 1-2 2h-3\" />\n </svg>\n )}\n </button>\n </div>\n\n {/* Issue #100/#106: draggable, resizable singleton chat popover.\n position:fixed so it floats above the page (overflow-hidden here\n doesn't clip fixed children). Rendered *inside* the container so\n that when the graph goes fullscreen (#81) the popover joins the\n fullscreen subtree and stays visible — a sibling render would be\n outside the fullscreened element and disappear. */}\n {chatAlias && (\n <ChatPopover alias={chatAlias} onClose={() => setChatAlias(null)} />\n )}\n </div>\n </section>\n );\n}\n","'use client';\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { AliasAvatar } from './AliasAvatar';\nimport { TaskChatPanel } from './TaskChatPanel';\nimport { useSessions } from '../lib/hooks';\nimport { relativeAgo } from '../lib/time';\n\ninterface ChatPopoverProps {\n /** Node alias to chat with. Changing it switches the conversation. */\n alias: string;\n onClose: () => void;\n}\n\nconst MAX_W = 380;\nconst MAX_H = 520;\nconst MIN_W = 300;\nconst MIN_H = 320;\nconst MARGIN = 16;\nconst MOBILE_BP = 640;\n\n/**\n * Issue #100: a floating, draggable chat window opened by clicking a node\n * avatar in the topology graph. Singleton — the parent (TopoGraph) keeps a\n * single `chatAlias`, so clicking another node just swaps `alias` here and\n * the conversation switches in place.\n *\n * Issue #106: a bottom-right handle resizes the window (drag to change w/h,\n * clamped to MIN_W/MIN_H .. the viewport). Coexists with the header move-drag\n * and the close button — each interaction lives on a distinct element and\n * stops its own pointerdown from reaching the others.\n *\n * One consistent design across viewports: a floating draggable card sized to\n * fit (`min(380, vw-32) × min(520, vh-96)`). On a phone it ends up near\n * full-width, so it's docked low on open — the graph above stays visible, and\n * to chat with a different node you drag it down / tap an exposed avatar.\n *\n * The chat body reuses TaskChatPanel's `inline` mode — send / SSE-receive /\n * history are already solved there; this component only adds the floating\n * shell + drag/resize behaviour.\n */\n/** Round 37 / Loop: surface node metadata (cwd + last-seen) inside the\n * ChatPopover header. The SVG <title> tooltip (Rounds 33-34) only shows\n * on hover-over-node, which is lost once the popover is open and\n * potentially dragged away. Lifting cwd and last-seen into the popover\n * header keeps the context where the user actually needs it.\n *\n * Round 38: relativeAgo factored to app/lib/time.ts so this file shares\n * the same TZ-safe parser as TopoGraph (was a duplicated mirror until\n * this round). */\n\nexport function ChatPopover({ alias, onClose }: ChatPopoverProps) {\n // Position + size are resolved on mount (SSR-safe defaults here).\n const [pos, setPos] = useState({ x: 0, y: 0 });\n const [size, setSize] = useState({ w: MAX_W, h: MAX_H });\n const dragRef = useRef<{ active: boolean; startX: number; startY: number; baseX: number; baseY: number }>({\n active: false, startX: 0, startY: 0, baseX: 0, baseY: 0,\n });\n const resizeRef = useRef<{ active: boolean; startX: number; startY: number; baseW: number; baseH: number }>({\n active: false, startX: 0, startY: 0, baseW: 0, baseH: 0,\n });\n\n // Round 37 / Loop: pull the target session out of the SWR cache that\n // TopoGraph already populates — same URL, so no extra network fetch.\n const { sessions } = useSessions();\n const session = useMemo(() => sessions.find(s => s.alias === alias), [sessions, alias]);\n const isOnline = !!session && session.status !== 'offline';\n const lastSeenLine = !isOnline ? relativeAgo(session?.last_seen_at) : null;\n\n const clamp = useCallback((x: number, y: number, w: number, h: number) => {\n const maxX = Math.max(MARGIN, window.innerWidth - w - MARGIN);\n const maxY = Math.max(MARGIN, window.innerHeight - h - MARGIN);\n return {\n x: Math.min(Math.max(MARGIN, x), maxX),\n y: Math.min(Math.max(MARGIN, y), maxY),\n };\n }, []);\n\n useEffect(() => {\n const place = () => {\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n const w = Math.min(MAX_W, vw - MARGIN * 2);\n const h = Math.min(MAX_H, vh - MARGIN * 6);\n setSize({ w, h });\n const mobile = vw < MOBILE_BP;\n // Desktop: top-right of the graph. Mobile: docked low so the graph\n // above stays visible — switch nodes by tapping an exposed avatar.\n const x = mobile ? MARGIN : vw - w - 24;\n const y = mobile ? vh - h - MARGIN : 96;\n setPos(clamp(x, y, w, h));\n };\n place();\n // Keep the popover on-screen if the window is resized.\n window.addEventListener('resize', place);\n return () => window.removeEventListener('resize', place);\n }, [clamp]);\n\n // Esc closes — matches the rest of the dashboard's overlay convention.\n useEffect(() => {\n const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [onClose]);\n\n const onPointerDown = (e: React.PointerEvent<HTMLDivElement>) => {\n if (e.button !== 0) return;\n (e.currentTarget as Element).setPointerCapture?.(e.pointerId);\n dragRef.current = { active: true, startX: e.clientX, startY: e.clientY, baseX: pos.x, baseY: pos.y };\n };\n const onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {\n const d = dragRef.current;\n if (!d.active) return;\n setPos(clamp(d.baseX + (e.clientX - d.startX), d.baseY + (e.clientY - d.startY), size.w, size.h));\n };\n const onPointerUp = (e: React.PointerEvent<HTMLDivElement>) => {\n if (!dragRef.current.active) return;\n dragRef.current.active = false;\n try { (e.currentTarget as Element).releasePointerCapture?.(e.pointerId); } catch {}\n };\n\n // Resize from the bottom-right handle. stopPropagation keeps the header\n // move-drag out of it; the popover is top-left anchored so growing it can\n // only push the bottom/right edges, which we clamp to the viewport.\n const onResizeDown = (e: React.PointerEvent<HTMLDivElement>) => {\n if (e.button !== 0) return;\n e.stopPropagation();\n (e.currentTarget as Element).setPointerCapture?.(e.pointerId);\n resizeRef.current = { active: true, startX: e.clientX, startY: e.clientY, baseW: size.w, baseH: size.h };\n };\n const onResizeMove = (e: React.PointerEvent<HTMLDivElement>) => {\n const r = resizeRef.current;\n if (!r.active) return;\n e.stopPropagation();\n const maxW = Math.max(MIN_W, window.innerWidth - pos.x - MARGIN);\n const maxH = Math.max(MIN_H, window.innerHeight - pos.y - MARGIN);\n setSize({\n w: Math.min(maxW, Math.max(MIN_W, r.baseW + (e.clientX - r.startX))),\n h: Math.min(maxH, Math.max(MIN_H, r.baseH + (e.clientY - r.startY))),\n });\n };\n const onResizeUp = (e: React.PointerEvent<HTMLDivElement>) => {\n if (!resizeRef.current.active) return;\n resizeRef.current.active = false;\n e.stopPropagation();\n try { (e.currentTarget as Element).releasePointerCapture?.(e.pointerId); } catch {}\n };\n\n return (\n <div\n className=\"fixed z-50 flex flex-col overflow-hidden rounded-xl border border-[var(--border)] bg-[var(--bg)] shadow-2xl shadow-black/60 anet-fade-in\"\n style={{ left: pos.x, top: pos.y, width: size.w, height: size.h }}\n role=\"dialog\"\n aria-label={`Chat with ${alias}`}\n >\n {/* Drag handle — the whole header bar moves the window. */}\n <div\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerCancel={onPointerUp}\n className=\"flex items-center justify-between px-3 py-2.5 border-b border-[var(--border)] bg-[var(--bg-secondary)] rounded-t-xl cursor-grab active:cursor-grabbing select-none touch-none\"\n >\n <div className=\"flex items-center gap-2.5 min-w-0\">\n <AliasAvatar alias={alias} size={28} />\n <div className=\"min-w-0\">\n <div className=\"text-sm font-semibold text-[var(--fg)] truncate\">{alias}</div>\n {/* Round 37: cwd / last-seen lines surface the same metadata\n the SVG <title> tooltip carries (rounds 33-34), but inside\n the popover where it stays accessible after dragging the\n window away from the node. Fall back to the drag-hint\n when no metadata is reported. */}\n {session?.project_dir ? (\n <div className=\"text-[10px] text-[var(--fg-muted)] truncate font-mono\" title={session.project_dir} data-popover-cwd>\n cwd: {session.project_dir}\n </div>\n ) : null}\n {lastSeenLine ? (\n <div className=\"text-[10px] text-[var(--fg-muted)] truncate\" data-popover-lastseen>\n last seen: {lastSeenLine}\n </div>\n ) : null}\n {!session?.project_dir && !lastSeenLine ? (\n <div className=\"text-[10px] text-[var(--fg-muted)]\">Drag to move · Esc to close</div>\n ) : null}\n </div>\n </div>\n <button\n onClick={onClose}\n // Without this the header's drag handler captures the pointer and\n // Chromium retargets the click to the header — the button never fires.\n onPointerDown={(e) => e.stopPropagation()}\n aria-label=\"Close chat\"\n className=\"text-[var(--fg-muted)] hover:text-[var(--fg)] p-1.5 rounded-lg hover:bg-[var(--bg-elevated)] shrink-0\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n {/* Chat body — TaskChatPanel inline mode owns send / SSE / history. */}\n <div className=\"flex-1 min-h-0\">\n <TaskChatPanel alias={alias} onClose={onClose} inline />\n </div>\n\n {/* Issue #106: bottom-right resize handle. */}\n <div\n onPointerDown={onResizeDown}\n onPointerMove={onResizeMove}\n onPointerUp={onResizeUp}\n onPointerCancel={onResizeUp}\n aria-label=\"Resize chat\"\n className=\"absolute bottom-0 right-0 w-5 h-5 cursor-nwse-resize touch-none z-10\"\n style={{ touchAction: 'none' }}\n >\n <svg className=\"absolute bottom-0.5 right-0.5 w-3 h-3 text-[var(--fg-dim)]\" viewBox=\"0 0 12 12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.5\" strokeLinecap=\"round\" aria-hidden>\n <path d=\"M11 5L5 11M11 9L9 11\" />\n </svg>\n </div>\n </div>\n );\n}\n","/** Issue #96: node visual identity — model vendor + runtime.\n *\n * Single source for two orthogonal node dimensions:\n * - vendor : which model house powers the node (avatar body)\n * - runtime: which execution shell it runs in (corner badge)\n *\n * Both are driven by the server `model` / `runtime` fields added in\n * commhub-server 0.8.1-preview.0 (#96 Phase 1, commit 08482ef). Old nodes\n * report `model: null` / `runtime: null` and fall back gracefully — never a\n * broken image or blank avatar.\n *\n * Adding a vendor = one entry in VENDOR_RULES. Dropping a real logo file into\n * public/vendors/ + setting `logo` lights it up with zero render-code change;\n * until then a vendor-tinted monogram stands in (not a fake official logo).\n */\n\nexport interface VendorIdentity {\n id: string;\n label: string;\n /** Monogram fallback colours, used when `logo` is null. */\n mono: { bg: string; ring: string; text: string };\n /** 1-char monogram shown when there's no logo asset. */\n initial: string;\n /** Packaged logo asset path, or null → render the monogram instead. */\n logo: string | null;\n}\n\nconst UNKNOWN_VENDOR: VendorIdentity = {\n id: 'unknown',\n label: 'Unknown vendor',\n mono: { bg: 'hsl(220 10% 26%)', ring: 'hsl(220 10% 46%)', text: 'hsl(220 14% 82%)' },\n initial: '?',\n logo: null,\n};\n\n// Ordered prefix rules — first match wins. `test` runs against a lowercased\n// model id. Keep the most specific prefixes first.\nconst VENDOR_RULES: Array<{ test: (m: string) => boolean; vendor: VendorIdentity }> = [\n {\n test: (m) => m.startsWith('intern'),\n vendor: {\n id: 'intern',\n label: '书生 · 上海 AI 实验室',\n mono: { bg: 'hsl(28 38% 24%)', ring: 'hsl(32 45% 52%)', text: 'hsl(34 60% 82%)' },\n initial: '书',\n // #79 shipped this asset — reuse it as the 书生 vendor logo.\n logo: '/intern_avatar.png',\n },\n },\n {\n test: (m) => m.startsWith('minimax'),\n vendor: {\n id: 'minimax',\n label: 'MiniMax',\n mono: { bg: 'hsl(18 50% 26%)', ring: 'hsl(18 65% 52%)', text: 'hsl(20 80% 82%)' },\n initial: 'M',\n // P0 (Vincent 5222) custom-designed vendor badge — NOT a copy of\n // the MiniMax trademark; geometric min/max zigzag in their warm-\n // red palette. Replaces plain-letter \"M\" fallback.\n logo: '/vendors/minimax.svg',\n },\n },\n {\n test: (m) => m.startsWith('claude'),\n vendor: {\n id: 'anthropic',\n label: 'Anthropic',\n mono: { bg: 'hsl(16 32% 26%)', ring: 'hsl(16 48% 54%)', text: 'hsl(18 60% 84%)' },\n initial: 'A',\n // P0 (Vincent 5222) custom-designed vendor badge — NOT a copy of\n // the Anthropic trademark; 4-pointed sparkle in their warm-orange\n // palette evokes AI/Claude without imitating the official mark.\n // Real Anthropic logo still pending Vincent-direct asset OK.\n logo: '/vendors/claude.svg',\n },\n },\n {\n test: (m) => m.startsWith('gpt') || m.startsWith('codex') || m.startsWith('o1') || m.startsWith('o3') || m.startsWith('o4'),\n vendor: {\n id: 'openai',\n label: 'OpenAI',\n mono: { bg: 'hsl(165 26% 22%)', ring: 'hsl(165 40% 44%)', text: 'hsl(165 45% 80%)' },\n initial: 'O',\n // P0 (Vincent 5222) custom-designed vendor badge — NOT a copy of\n // the OpenAI trademark; hexagonal frame + center dot in their\n // teal palette evokes geometric AI lattice without imitating the\n // knot. Real OpenAI logo still pending Vincent-direct asset OK.\n logo: '/vendors/openai.svg',\n },\n },\n];\n\n/** Resolve a model id to its vendor identity. `null` / unmatched → UNKNOWN. */\nexport function vendorForModel(model: string | null | undefined): VendorIdentity {\n if (!model) return UNKNOWN_VENDOR;\n const m = model.toLowerCase();\n for (const rule of VENDOR_RULES) {\n if (rule.test(m)) return rule.vendor;\n }\n return UNKNOWN_VENDOR;\n}\n\nexport type Runtime = 'claude-code-cli' | 'codex-sdk' | 'claude-agent-sdk' | 'http-api';\n\nexport interface RuntimeIdentity {\n label: string;\n /** SVG path(s) drawn inside a 0..24 viewBox for the corner badge. */\n iconPath: string;\n /** Badge accent colour — kept clear of working/idle/offline status hues. */\n color: string;\n}\n\nconst RUNTIME_MAP: Record<Runtime, RuntimeIdentity> = {\n // terminal / CLI\n 'claude-code-cli': {\n label: 'Claude Code CLI',\n iconPath: 'M4 5h16v14H4z M7 9l3 3-3 3 M13 15h4',\n color: '#a78bfa',\n },\n // code-block / SDK\n 'codex-sdk': {\n label: 'Codex SDK',\n iconPath: 'M9 7l-5 5 5 5 M15 7l5 5-5 5',\n color: '#38bdf8',\n },\n // SDK, distinct hue from codex\n 'claude-agent-sdk': {\n label: 'Claude Agent SDK',\n iconPath: 'M4 7h16v10H4z M8 11h8 M8 14h5',\n color: '#34d399',\n },\n // cloud / API — non-resident process\n 'http-api': {\n label: 'HTTP API',\n iconPath: 'M7 18a4 4 0 010-8 5 5 0 019.6-1.3A3.5 3.5 0 0118 18z',\n color: '#fbbf24',\n },\n};\n\n/** Resolve a server runtime string to badge identity. Unknown → null. */\nexport function runtimeIdentity(runtime: string | null | undefined): RuntimeIdentity | null {\n if (!runtime) return null;\n return RUNTIME_MAP[runtime as Runtime] ?? null;\n}\n\n/** Compact one-line identity for hover / detail surfaces:\n * \"Anthropic · claude-opus-4 · Claude Code CLI\". Pieces with no data drop. */\nexport function identityLine(model: string | null | undefined, runtime: string | null | undefined): string {\n const v = vendorForModel(model);\n const r = runtimeIdentity(runtime);\n const parts: string[] = [];\n if (v.id !== 'unknown') parts.push(v.label);\n if (model) parts.push(model);\n if (r) parts.push(r.label);\n return parts.join(' · ');\n}\n","'use client';\n\nimport Link from 'next/link';\nimport { Session } from './types';\nimport { timeAgo } from './utils';\nimport { AliasAvatar } from './AliasAvatar';\n\ninterface AgentCardProps {\n session: Session;\n hasSse: boolean;\n sseCount: number;\n onChat?: (alias: string) => void;\n}\n\nconst STATUS_CONFIG: Record<string, { bg: string; text: string; dot: string; glow: string }> = {\n working: { bg: 'bg-green-900/30 border-green-800/30', text: 'text-green-300', dot: 'bg-green-500', glow: 'shadow-green-500/10' },\n idle: { bg: 'bg-cyan-900/30 border-cyan-800/30', text: 'text-cyan-300', dot: 'bg-cyan-400', glow: 'shadow-cyan-500/5' },\n blocked: { bg: 'bg-yellow-900/30 border-yellow-800/30', text: 'text-yellow-300', dot: 'bg-yellow-500', glow: '' },\n error: { bg: 'bg-red-900/30 border-red-800/30', text: 'text-red-300', dot: 'bg-red-500', glow: '' },\n};\n\nconst DEFAULT_STATUS = { bg: 'bg-gray-800/50 border-gray-700/30', text: 'text-gray-500', dot: 'bg-gray-500', glow: '' };\n\nexport function AgentCard({ session: s, hasSse, sseCount, onChat }: AgentCardProps) {\n const cfg = hasSse ? (STATUS_CONFIG[s.status] || DEFAULT_STATUS) : DEFAULT_STATUS;\n\n return (\n <Link\n href={`/node?alias=${encodeURIComponent(s.alias)}`}\n prefetch={false}\n className={`anet-agent-card group relative block rounded-xl border p-4 transition-all duration-300 cursor-pointer hover:-translate-y-0.5 ${\n hasSse\n ? `bg-[#111128] border-[#2a2a4a] hover:border-cyan-500/30 hover:shadow-lg ${cfg.glow}`\n : 'bg-[#0d0d1a] border-[#1a1a2a] opacity-40'\n }`}\n >\n {/* Header: avatar + name + status. Avatar carries the alias→hue map\n shared with Messages/Nodes/Tasks/Overview; the live status dot\n stays as a small pulse-capable indicator. */}\n <div className=\"flex items-center justify-between mb-3\">\n <div className=\"flex items-center gap-2 min-w-0\">\n <AliasAvatar alias={s.alias} size={22} />\n <span className=\"font-semibold text-white truncate text-sm\" title={s.alias}>{s.alias}</span>\n <span className={`inline-block w-1.5 h-1.5 rounded-full shrink-0 ${cfg.dot} ${hasSse && s.status === 'working' ? 'animate-pulse' : ''}`} />\n </div>\n <span className={`text-[11px] px-2 py-0.5 rounded-md border shrink-0 ${cfg.bg} ${cfg.text}`}>\n {hasSse ? s.status : 'offline'}\n </span>\n </div>\n\n {/* Agent type badge */}\n <div className=\"flex items-center gap-2 mb-3\">\n <span className=\"text-xs text-gray-600 bg-[#0a0a15] px-2 py-0.5 rounded border border-[#1a1a2a]\">\n {s.agent || 'unknown'}\n </span>\n {hasSse && (\n <span className=\"text-[10px] text-green-500\">SSE:{sseCount}</span>\n )}\n </div>\n\n {/* Task */}\n {s.task ? (\n <div className=\"text-xs text-gray-400 bg-[#0a0a15] rounded-lg px-3 py-2 border border-[#1a1a2a] line-clamp-2\" title={s.task}>\n {s.task}\n </div>\n ) : (\n <div className=\"text-xs text-gray-700 italic\">No active task</div>\n )}\n\n {/* Progress bar */}\n {s.progress > 0 && (\n <div className=\"mt-3\">\n <div className=\"flex justify-between text-[10px] mb-1\">\n <span className=\"text-gray-600\">Progress</span>\n <span className={cfg.text}>{s.progress}%</span>\n </div>\n <div className=\"w-full bg-gray-800 rounded-full h-1.5 overflow-hidden\">\n <div\n className={`h-1.5 rounded-full transition-all duration-700 ${s.status === 'working' ? 'bg-green-500' : 'bg-cyan-500'}`}\n style={{ width: `${Math.min(s.progress, 100)}%` }}\n />\n </div>\n </div>\n )}\n\n {/* Footer: time + chat + hover chevron affordance (round 44).\n The card is a <Link> so it's clickable everywhere, but with no\n visible cue users may not realise. Chevron appears on hover and\n slides right ~2px for a \"drill in\" hint. */}\n <div className=\"mt-3 flex justify-between items-center text-[10px] text-gray-600\">\n <span className=\"truncate\" title={s.server || ''}>{s.server || '--'}</span>\n <div className=\"flex items-center gap-2\">\n {onChat && hasSse && (\n <button\n onClick={e => { e.preventDefault(); e.stopPropagation(); onChat(s.alias); }}\n className=\"text-cyan-400 hover:text-cyan-300 px-1.5 py-0.5 rounded border border-cyan-500/20 hover:bg-cyan-500/10 transition-colors\"\n >\n Chat\n </button>\n )}\n <span>{timeAgo(s.updated_at)}</span>\n <svg\n aria-hidden\n className=\"w-3 h-3 text-gray-700 opacity-0 -translate-x-1 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-200\"\n fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"\n >\n <path d=\"M9 18l6-6-6-6\" />\n </svg>\n </div>\n </div>\n </Link>\n );\n}\n","'use client';\n\nimport { InboxMessage } from './types';\nimport { AliasAvatar } from './AliasAvatar';\nimport { timeAgo, previewContent } from './utils';\n\ninterface InboxPanelProps {\n messages: InboxMessage[];\n}\n\nexport function InboxPanel({ messages }: InboxPanelProps) {\n if (messages.length === 0) return null;\n\n return (\n <div className=\"mt-8\">\n <h2 className=\"text-lg font-semibold text-white mb-3 flex items-center gap-2\">\n <span className=\"text-gray-500\">Inbox</span>\n <span className=\"text-xs bg-blue-900/30 text-blue-400 px-2 py-0.5 rounded-full border border-blue-800/30\">\n {messages.length}\n </span>\n </h2>\n <div className=\"space-y-2 max-h-96 overflow-y-auto pr-1 scrollbar-thin\">\n {messages.map(m => (\n <div key={m.id} className=\"bg-[#111128] border border-[#2a2a4a] rounded-lg px-4 py-3 text-sm transition-colors hover:border-[#3a3a5a]\">\n <div className=\"flex items-center gap-2 text-xs mb-1.5\">\n {m.from_session && <AliasAvatar alias={m.from_session} size={16} />}\n <span className=\"text-gray-200 font-medium truncate\">{m.from_session}</span>\n <span className=\"ml-auto text-gray-600 shrink-0\" title={m.created_at}>{timeAgo(m.created_at)}</span>\n </div>\n <div className=\"text-gray-300 leading-relaxed line-clamp-3\" title={m.content || ''}>{previewContent(m.content)}</div>\n </div>\n ))}\n </div>\n </div>\n );\n}\n","'use client';\n\n/**\n * Loading skeleton — mirrors the actual Overview layout structure so the\n * page doesn't appear to \"shift\" once data arrives. Uses the same\n * `anet-skeleton-pulse` rhythm as the brand pulse (1.6s opacity drift,\n * no scale, no blur, no glow) so the loading animation feels native to\n * the rest of the dashboard rather than a generic spinner.\n *\n * Bars are theme-aware: anet-skeleton-bar (existing CSS shim resolves this to\n * var(--bg-elevated) on light/mint), so light theme shows soft grey blocks\n * on white cards and dark theme shows lighter blocks on navy cards.\n */\nexport function LoadingSkeleton() {\n return (\n <div className=\"min-h-screen bg-[#0a0a1a] text-gray-100 p-4 sm:p-6 font-mono\">\n {/* KPI top strip — 4 cards matching StatsBar */}\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-3 mb-8 anet-skeleton-pulse\">\n {[1, 2, 3, 4].map(i => (\n <div key={i} className=\"rounded-xl border border-[#2a2a4a] bg-[#111128] px-4 py-3\">\n <Bar w=\"2.5rem\" h=\"1.75rem\" />\n <Bar w=\"3.5rem\" h=\"0.75rem\" className=\"mt-2\" />\n <Bar w=\"5rem\" h=\"0.625rem\" className=\"mt-1\" />\n </div>\n ))}\n </div>\n\n {/* Dispatch + UserBar row */}\n <div className=\"flex items-center gap-3 mb-3 anet-skeleton-pulse\">\n <Bar w=\"6rem\" h=\"2.5rem\" rounded=\"0.75rem\" />\n <div className=\"flex-1 rounded-lg border border-[#2a2a4a] bg-[#111128] px-4 py-2.5 flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-full anet-skeleton-bar\" />\n <div className=\"flex-1\">\n <Bar w=\"6rem\" h=\"0.875rem\" />\n <Bar w=\"10rem\" h=\"0.625rem\" className=\"mt-1\" />\n </div>\n </div>\n </div>\n\n {/* Config bar */}\n <div className=\"mb-6 rounded-lg border border-[#2a2a4a] bg-[#111128] px-4 py-3 anet-skeleton-pulse\">\n <Bar w=\"14rem\" h=\"0.875rem\" />\n </div>\n\n {/* Stat strip 3 cards */}\n <div className=\"grid grid-cols-3 gap-2 sm:gap-3 mb-3 anet-skeleton-pulse\">\n {[1, 2, 3].map(i => (\n <div key={i} className=\"rounded-xl border border-[#2a2a4a] bg-[#111128] px-3 py-3\">\n <Bar w=\"2rem\" h=\"1.25rem\" />\n <Bar w=\"2.5rem\" h=\"0.75rem\" className=\"mt-1\" />\n <Bar w=\"3.5rem\" h=\"0.625rem\" className=\"mt-px\" />\n </div>\n ))}\n </div>\n\n {/* Nav rail 3 cards */}\n <div className=\"grid grid-cols-3 gap-2 sm:gap-3 mb-6 anet-skeleton-pulse\">\n {[1, 2, 3].map(i => (\n <div key={i} className=\"rounded-xl border border-[#2a2a4a] bg-[#111128] px-3 py-2.5 flex items-center justify-center gap-2\">\n <div className=\"w-4 h-4 rounded anet-skeleton-bar\" />\n <Bar w=\"4rem\" h=\"0.75rem\" />\n </div>\n ))}\n </div>\n\n {/* Broadcast bar */}\n <div className=\"mb-6 flex gap-2 anet-skeleton-pulse\">\n <div className=\"flex-1 h-10 rounded-lg border border-[#2a2a4a] bg-[#111128]\" />\n <div className=\"w-28 h-10 rounded-lg anet-skeleton-bar\" />\n </div>\n\n {/* Agent card grid */}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-3 sm:gap-4 anet-skeleton-pulse\">\n {[1, 2, 3, 4].map(i => (\n <div key={i} className=\"rounded-xl border border-[#2a2a4a] bg-[#111128] p-4\">\n <div className=\"flex items-center gap-2 mb-3\">\n <div className=\"w-2 h-2 rounded-full anet-skeleton-bar\" />\n <Bar w=\"6rem\" h=\"0.875rem\" />\n </div>\n <div className=\"space-y-2\">\n <Bar w=\"100%\" h=\"0.625rem\" />\n <Bar w=\"75%\" h=\"0.625rem\" />\n <Bar w=\"55%\" h=\"0.625rem\" />\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n}\n\n/** Single shimmer bar — uses `anet-skeleton-bar` so theme-specific bar\n * color (dark navy on dark themes, mid-grey on light) overrides the\n * default. */\nfunction Bar({ w, h, rounded, className }: { w: string; h: string; rounded?: string; className?: string }) {\n return (\n <div\n className={`anet-skeleton-bar ${className || ''}`}\n style={{ width: w, height: h, borderRadius: rounded || '0.375rem' }}\n />\n );\n}\n\n// EmptyState export removed in 0.4.5 — replaced by EmptyState.tsx with\n// per-variant glyphs and a NodesEmptyState wrapper for the Overview\n// hint-aware behavior. See app/components/EmptyState.tsx.\n","'use client';\n\nimport { useEffect, useState } from 'react';\nimport { useNetworkId } from '../lib/network-context';\n\ninterface User {\n user_id: string;\n username: string;\n display_name: string;\n role: string;\n}\n\ninterface Network {\n network_id: string;\n network_name: string;\n description: string;\n}\n\ninterface AuthState {\n user: User | null;\n networks: Network[];\n currentNetwork: string;\n token: string;\n}\n\nexport function UserBar() {\n const { setNetworkId } = useNetworkId();\n const [auth, setAuth] = useState<AuthState>({ user: null, networks: [], currentNetwork: '', token: '' });\n const [showLogin, setShowLogin] = useState(false);\n const [username, setUsername] = useState('');\n const [password, setPassword] = useState('');\n const [loginError, setLoginError] = useState('');\n const [loginPending, setLoginPending] = useState(false);\n\n // Try to restore from sessionStorage\n useEffect(() => {\n const saved = sessionStorage.getItem('anet_v3_auth');\n if (saved) {\n try { setAuth(JSON.parse(saved)); } catch {}\n }\n }, []);\n\n const doAuth = async (action: 'login' | 'register') => {\n if (!username.trim() || !password.trim()) return;\n setLoginPending(true);\n setLoginError('');\n try {\n const res = await fetch('/api/hub/auth', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action, username, password }),\n });\n const data = await res.json();\n if (!data.ok) { setLoginError(data.error || 'Failed'); setLoginPending(false); return; }\n\n // Fetch user details\n const meRes = await fetch(`/api/hub/auth?token=${data.token}&endpoint=/api/auth/me`);\n const meData = await meRes.json();\n\n const newAuth: AuthState = {\n user: data.user,\n networks: meData.networks || [],\n currentNetwork: meData.current_network?.network_id || meData.networks?.[0]?.network_id || '',\n token: data.token,\n };\n setAuth(newAuth);\n sessionStorage.setItem('anet_v3_auth', JSON.stringify(newAuth));\n setShowLogin(false);\n setUsername('');\n setPassword('');\n } catch { setLoginError('Connection failed'); }\n setLoginPending(false);\n };\n\n const logout = () => {\n setAuth({ user: null, networks: [], currentNetwork: '', token: '' });\n sessionStorage.removeItem('anet_v3_auth');\n };\n\n const [editing, setEditing] = useState(false);\n const [editName, setEditName] = useState('');\n const [editEmail, setEditEmail] = useState('');\n\n const updateProfile = async () => {\n try {\n const res = await fetch(`/api/hub/auth?token=${auth.token}&endpoint=/api/auth/me`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action: 'update_profile', token: auth.token, display_name: editName, email: editEmail }),\n });\n // Use a direct PUT via a new proxy approach\n const putRes = await fetch('/api/hub/auth', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ action: 'update_profile', token: auth.token, display_name: editName, email: editEmail }),\n });\n const data = await putRes.json();\n if (data.ok && data.user) {\n const updated = { ...auth, user: data.user };\n setAuth(updated);\n sessionStorage.setItem('anet_v3_auth', JSON.stringify(updated));\n setEditing(false);\n }\n } catch {}\n };\n\n // Don't show anything when not logged in — login page handles that\n if (!auth.user) return null;\n\n const initials = (auth.user.display_name || auth.user.username).slice(0, 2).toUpperCase();\n\n return (\n <div className=\"flex items-center justify-between bg-[#111128] border border-[#2a2a4a] rounded-lg px-4 py-2.5 mb-4\">\n {/* User info */}\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-full bg-gradient-to-br from-cyan-500 to-blue-600 flex items-center justify-center text-xs font-bold text-white\">\n {initials}\n </div>\n <div>\n <div className=\"text-sm text-white font-medium\">{auth.user.display_name || auth.user.username}</div>\n <div className=\"text-[10px] text-gray-500\">{auth.user.role} · {auth.user.user_id}</div>\n </div>\n </div>\n\n {/* Network switcher */}\n <div className=\"flex items-center gap-3\">\n {auth.networks.length > 0 && (\n <select\n value={auth.currentNetwork}\n onChange={e => {\n const updated = { ...auth, currentNetwork: e.target.value };\n setAuth(updated);\n setNetworkId(e.target.value);\n sessionStorage.setItem('anet_v3_auth', JSON.stringify(updated));\n }}\n className=\"bg-[#0a0a15] border border-[#2a2a4a] rounded px-2 py-1 text-xs text-white focus:outline-none\"\n >\n {auth.networks.map(n => (\n <option key={n.network_id} value={n.network_id}>{n.network_name}</option>\n ))}\n </select>\n )}\n <button onClick={() => { setEditName(auth.user?.display_name || ''); setEditEmail(''); setEditing(!editing); }}\n className=\"text-xs text-gray-500 hover:text-gray-300 whitespace-nowrap\"\n aria-label=\"Edit profile\">\n <span className=\"hidden sm:inline\">Edit</span>\n <svg className=\"sm:hidden w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth=\"1.5\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 011.13-1.897L16.862 4.487z\" />\n </svg>\n </button>\n <button onClick={logout}\n className=\"text-xs text-gray-500 hover:text-gray-300 whitespace-nowrap\"\n aria-label=\"Sign out\">\n <span className=\"hidden sm:inline\">Sign out</span>\n <svg className=\"sm:hidden w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth=\"1.5\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15m3 0l3-3m0 0l-3-3m3 3H9\" />\n </svg>\n </button>\n </div>\n {editing && (\n <div className=\"flex flex-wrap items-center gap-2 mt-2 pt-2 border-t border-[#2a2a4a]\">\n <input type=\"text\" value={editName} onChange={e => setEditName(e.target.value)} placeholder=\"Display name\"\n className=\"bg-[#0a0a15] border border-[#2a2a4a] rounded px-2 py-1 text-xs text-white placeholder-gray-600 focus:outline-none w-32\" />\n <input type=\"email\" value={editEmail} onChange={e => setEditEmail(e.target.value)} placeholder=\"Email\"\n className=\"bg-[#0a0a15] border border-[#2a2a4a] rounded px-2 py-1 text-xs text-white placeholder-gray-600 focus:outline-none w-40\" />\n <button onClick={updateProfile} className=\"px-2 py-1 bg-cyan-600 hover:bg-cyan-500 text-white text-xs rounded\">Save</button>\n </div>\n )}\n </div>\n );\n}\n","'use client';\n\nimport { useState } from 'react';\nimport { TaskChatPanel } from './TaskChatPanel';\n\ninterface CommandCenterProps {\n /** Currently open chat tabs */\n tabs: string[];\n activeTab: string;\n onOpenTab: (alias: string) => void;\n onCloseTab: (alias: string) => void;\n onSetActive: (alias: string) => void;\n onClose: () => void;\n}\n\n/**\n * Multi-tab chat panel for commanding multiple agents simultaneously.\n * Wraps TaskChatPanel with a tab bar for switching between agents.\n */\nexport function CommandCenter({ tabs, activeTab, onOpenTab, onCloseTab, onSetActive, onClose }: CommandCenterProps) {\n if (tabs.length === 0) return null;\n\n return (\n <>\n {/* Backdrop */}\n <div className=\"fixed inset-0 bg-black/30 z-40 lg:hidden\" onClick={onClose} />\n\n {/* Panel */}\n <div className=\"fixed top-0 right-0 h-full w-full lg:w-[500px] bg-[#0a0a1a] border-l border-[#2a2a4a] z-50 flex flex-col shadow-2xl shadow-black/60 animate-slide-in\">\n {/* Tab bar */}\n <div className=\"flex items-center border-b border-[#2a2a4a] bg-[#0d0d1a] overflow-x-auto\">\n <div className=\"flex-1 flex min-w-0\">\n {tabs.map(alias => (\n <button\n key={alias}\n onClick={() => onSetActive(alias)}\n className={`flex items-center gap-1.5 px-3 py-2.5 text-xs border-b-2 transition-colors shrink-0 ${\n activeTab === alias\n ? 'border-cyan-400 text-cyan-300 bg-cyan-500/5'\n : 'border-transparent text-gray-500 hover:text-gray-300'\n }`}\n >\n <div className={`w-2 h-2 rounded-full ${activeTab === alias ? 'bg-cyan-400' : 'bg-gray-600'}`} />\n <span className=\"max-w-[80px] truncate\">{alias}</span>\n <button\n onClick={e => { e.stopPropagation(); onCloseTab(alias); }}\n className=\"ml-1 text-gray-600 hover:text-gray-300 p-0.5\"\n >\n ×\n </button>\n </button>\n ))}\n </div>\n <button onClick={onClose} className=\"text-gray-500 hover:text-white px-3 py-2.5 shrink-0 border-l border-[#2a2a4a]\">\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n {/* Active chat - render all but only show active (preserves state) */}\n <div className=\"flex-1 relative overflow-hidden\">\n {tabs.map(alias => (\n <div key={alias} className={`absolute inset-0 ${activeTab === alias ? 'block' : 'hidden'}`}>\n <InlineChat alias={alias} />\n </div>\n ))}\n </div>\n </div>\n\n <style jsx global>{`\n @keyframes slide-in {\n from { transform: translateX(100%); }\n to { transform: translateX(0); }\n }\n .animate-slide-in {\n animation: slide-in 0.2s ease-out;\n }\n `}</style>\n </>\n );\n}\n\n/** Inline chat without the panel chrome (used inside CommandCenter tabs) */\nfunction InlineChat({ alias }: { alias: string }) {\n // Reuse TaskChatPanel's logic but render without the outer frame\n return <TaskChatPanel alias={alias} onClose={() => {}} inline />;\n}\n\n/**\n * Hook to manage multi-tab command center state.\n */\nexport function useCommandCenter() {\n const [tabs, setTabs] = useState<string[]>([]);\n const [activeTab, setActiveTab] = useState('');\n\n const openTab = (alias: string) => {\n setTabs(prev => prev.includes(alias) ? prev : [...prev, alias]);\n setActiveTab(alias);\n };\n\n const closeTab = (alias: string) => {\n setTabs(prev => {\n const next = prev.filter(t => t !== alias);\n if (activeTab === alias) setActiveTab(next[next.length - 1] || '');\n return next;\n });\n };\n\n const closeAll = () => {\n setTabs([]);\n setActiveTab('');\n };\n\n return { tabs, activeTab, openTab, closeTab, closeAll, setActiveTab };\n}\n","'use client';\n\nimport { useState } from 'react';\nimport type { Session } from './types';\nimport { AliasAvatar } from './AliasAvatar';\n\ninterface DispatchPanelProps {\n sessions: Session[];\n onClose: () => void;\n}\n\ninterface SendResult {\n alias: string;\n ok: boolean;\n error?: string;\n}\n\nexport function DispatchPanel({ sessions, onClose }: DispatchPanelProps) {\n const [selected, setSelected] = useState<Set<string>>(new Set());\n const [prompt, setPrompt] = useState('');\n const [priority, setPriority] = useState('normal');\n const [sending, setSending] = useState(false);\n const [results, setResults] = useState<SendResult[]>([]);\n const [filter, setFilter] = useState('');\n\n const onlineNodes = sessions.filter(s => s.status !== 'offline');\n const filtered = onlineNodes.filter(s =>\n !filter || s.alias.toLowerCase().includes(filter.toLowerCase()) || (s.agent || '').toLowerCase().includes(filter.toLowerCase())\n );\n\n const toggleNode = (alias: string) => {\n setSelected(prev => {\n const next = new Set(prev);\n if (next.has(alias)) next.delete(alias); else next.add(alias);\n return next;\n });\n };\n\n const selectAll = () => {\n if (selected.size === filtered.length) {\n setSelected(new Set());\n } else {\n setSelected(new Set(filtered.map(s => s.alias)));\n }\n };\n\n const dispatch = async () => {\n if (!prompt.trim() || selected.size === 0 || sending) return;\n setSending(true);\n setResults([]);\n\n const promises = [...selected].map(async alias => {\n try {\n const res = await fetch('/api/hub/send', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ alias, task: prompt, priority }),\n });\n const data = await res.json();\n return { alias, ok: !!data.ok, error: data.error };\n } catch (e) {\n return { alias, ok: false, error: 'send failed' };\n }\n });\n\n const res = await Promise.all(promises);\n setResults(res);\n setSending(false);\n };\n\n const successCount = results.filter(r => r.ok).length;\n\n return (\n <>\n <div className=\"fixed inset-0 bg-black/50 z-40 anet-fade-in\" onClick={onClose} />\n <div className=\"fixed inset-4 lg:inset-x-[15%] lg:inset-y-[5%] bg-[#0a0a1a] border border-[#2a2a4a] rounded-2xl z-50 flex flex-col shadow-2xl shadow-black/70 overflow-hidden anet-fade-in\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-6 py-4 border-b border-[#2a2a4a] bg-[#0d0d1a]\">\n <div>\n <h2 className=\"text-lg font-bold text-white\">Dispatch Task</h2>\n <p className=\"text-xs text-gray-500 mt-0.5\">Send a task to one or more agents</p>\n </div>\n <button onClick={onClose} className=\"text-gray-500 hover:text-white p-1.5 rounded-lg hover:bg-[#1a1a2a]\">\n <svg className=\"w-5 h-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n <div className=\"flex-1 flex flex-col lg:flex-row overflow-hidden\">\n {/* Left: Node selection */}\n <div className=\"lg:w-[280px] border-b lg:border-b-0 lg:border-r border-[#2a2a4a] flex flex-col\">\n <div className=\"px-4 py-3 border-b border-[#2a2a4a]\">\n <input\n type=\"text\" value={filter} onChange={e => setFilter(e.target.value)}\n placeholder=\"Filter agents...\"\n className=\"w-full bg-[#111128] border border-[#2a2a4a] rounded-lg px-3 py-2 text-xs text-white placeholder-gray-600 focus:border-cyan-500/40 focus:outline-none\"\n />\n <div className=\"flex items-center justify-between mt-2\">\n <button onClick={selectAll} className=\"text-[10px] text-cyan-400 hover:text-cyan-300\">\n {selected.size === filtered.length ? 'Deselect all' : `Select all (${filtered.length})`}\n </button>\n <span className=\"text-[10px] text-gray-600\">{selected.size} selected</span>\n </div>\n </div>\n <div className=\"flex-1 overflow-y-auto px-2 py-2 space-y-0.5 max-h-[200px] lg:max-h-none\">\n {filtered.map(s => (\n <button key={s.alias} onClick={() => toggleNode(s.alias)}\n className={`w-full flex items-center gap-2 px-3 py-2 rounded-lg text-xs text-left transition-colors ${\n selected.has(s.alias) ? 'bg-cyan-500/10 text-cyan-300 border border-cyan-500/20' : 'text-gray-400 hover:bg-[#1a1a2a]'\n }`}>\n <AliasAvatar alias={s.alias} size={16} />\n <div className={`w-1.5 h-1.5 rounded-full shrink-0 ${s.status === 'working' ? 'bg-green-400' : s.status === 'idle' ? 'bg-cyan-400' : 'bg-gray-500'}`} />\n <span className=\"truncate flex-1\">{s.alias}</span>\n <span className=\"text-[9px] text-gray-600\">{s.agent || '--'}</span>\n </button>\n ))}\n {filtered.length === 0 && <div className=\"text-center text-xs text-gray-600 py-4\">No online agents</div>}\n </div>\n </div>\n\n {/* Right: Prompt + Send */}\n <div className=\"flex-1 flex flex-col\">\n <div className=\"flex-1 px-6 py-4 flex flex-col\">\n <label className=\"text-xs text-gray-500 uppercase mb-2\">Task Prompt</label>\n <textarea\n value={prompt} onChange={e => setPrompt(e.target.value)}\n placeholder=\"Enter the task you want to dispatch...\"\n className=\"flex-1 min-h-[120px] bg-[#111128] border border-[#2a2a4a] rounded-xl px-4 py-3 text-sm text-white placeholder-gray-600 focus:border-cyan-500/40 focus:outline-none resize-none\"\n />\n\n <div className=\"flex items-center gap-3 mt-4\">\n <select value={priority} onChange={e => setPriority(e.target.value)}\n className=\"bg-[#111128] border border-[#2a2a4a] rounded-lg px-3 py-2 text-xs text-white focus:outline-none\">\n <option value=\"normal\">Normal priority</option>\n <option value=\"high\">High priority</option>\n <option value=\"low\">Low priority</option>\n </select>\n\n <div className=\"flex-1\" />\n\n <button onClick={dispatch} disabled={sending || !prompt.trim() || selected.size === 0}\n className=\"px-6 py-2.5 bg-gradient-to-r from-cyan-600 to-blue-600 hover:from-cyan-500 hover:to-blue-500 disabled:from-gray-800 disabled:to-gray-800 disabled:text-gray-600 text-white text-sm font-medium rounded-xl transition-all shadow-lg shadow-cyan-500/10 disabled:shadow-none active:scale-95\">\n {sending ? (\n <span className=\"flex items-center gap-2\">\n <span className=\"w-4 h-4 border-2 border-white/30 border-t-white rounded-full animate-spin\" />\n Sending...\n </span>\n ) : (\n `Dispatch to ${selected.size} agent${selected.size > 1 ? 's' : ''}`\n )}\n </button>\n </div>\n </div>\n\n {/* Results */}\n {results.length > 0 && (\n <div className=\"px-6 py-3 border-t border-[#2a2a4a] bg-[#0d0d1a]\">\n <div className=\"text-xs text-gray-500 mb-2\">\n {successCount}/{results.length} dispatched successfully\n </div>\n <div className=\"flex flex-wrap gap-1.5\">\n {results.map(r => (\n <span key={r.alias} className={`flex items-center gap-1.5 text-[10px] pl-1 pr-2 py-0.5 rounded-full border ${\n r.ok ? 'text-green-300 border-green-500/20 bg-green-500/5' : 'text-red-300 border-red-500/20 bg-red-500/5'\n }`}>\n <AliasAvatar alias={r.alias} size={14} />\n <span>{r.alias}</span>\n <span aria-hidden>{r.ok ? '✓' : r.error || '✗'}</span>\n </span>\n ))}\n </div>\n </div>\n )}\n </div>\n </div>\n </div>\n </>\n );\n}\n"],"names":["React","_interopDefaultLegacy","e","React__default","_defineProperties","target","props","i","length","descriptor","enumerable","configurable","writable","Object","defineProperty","key","_createClass","Constructor","protoProps","staticProps","prototype","isProd","process","env","isString","o","toString","call","StyleSheet","param","ref","_name","name","_optimizeForSpeed","optimizeForSpeed","invariant$1","_deletedRulePlaceholder","_serverSheet","undefined","_tags","_injected","_rulesCount","node","document","querySelector","_nonce","_proto","setOptimizeForSpeed","bool","flush","inject","isOptimizeForSpeed","_this","cssRules","insertRule","rule","index","cssText","push","deleteRule","getSheetForTag","tag","sheet","styleSheets","ownerNode","getSheet","insertionPoint","replaceRule","trim","error","console","warn","makeStyleTag","cssString","relativeToTag","createElement","setAttribute","type","appendChild","createTextNode","head","getElementsByTagName","insertBefore","get","condition","message","Error","hash","str","_$hash","charCodeAt","stringHash","sanitize","replace","cache","computeId","baseId","propsToString","String","computeSelector","id","css","selectoPlaceholderRegexp","idcss","mapRulesToStyle","options","map","args","nonce","dangerouslySetInnerHTML","__html","StyleSheetRegistry","_styleSheet","styleSheet","_sheet","_fromServer","_indices","_instancesCounts","add","Array","isArray","children","getIdAndRules","styleId","rules","indices","filter","remove","invariant","tagFromServer","parentNode","removeChild","forEach","update","nextProps","fromServer","keys","concat","join","Boolean","styles","dynamic","selectFromServer","elements","slice","querySelectorAll","reduce","acc","element","StyleSheetContext","createContext","displayName","createStyleRegistry","StyleRegistry","configuredRegistry","registry","rootRegistry","useContext","useState","Provider","value","useStyleRegistry","useInsertionEffect","useLayoutEffect","defaultRegistry","JSXStyle","info","tagInfo","exports","style","module","StatsBar","online","working","total","version","uptime","onlinePercent","Math","round","fleetEmpty","className","StatCard","label","sub","color","accent","border","accentKey","split"],"mappings":"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","ignoreList":[0,1]}
|