Skip to content

feat(ui): add CognitiveMeshPanel, SystemHealthBar, and cogmesh-poller#3

Merged
JustAGhosT merged 2 commits intomainfrom
feat/cognitive-mesh-panel
Mar 31, 2026
Merged

feat(ui): add CognitiveMeshPanel, SystemHealthBar, and cogmesh-poller#3
JustAGhosT merged 2 commits intomainfrom
feat/cognitive-mesh-panel

Conversation

@JustAGhosT
Copy link
Copy Markdown
Contributor

@JustAGhosT JustAGhosT commented Mar 29, 2026

Summary

  • CognitiveMeshPanel — workflow cards with stage-progress bar, health badge, fallback indicator, view-result links; hidden with config message when unconfigured and no workflows
  • SystemHealthBar — service health pills in AgentFleetPanel header; hidden when all services unconfigured
  • cogmesh-poller — polls CM /health every 30 s, reloads on .retortconfig change, emits cogmesh:health:updated
  • parsers/retortconfig.ts — reads cogmesh.endpoint/secret with ${VAR} env substitution
  • protocol + bridge typesCognitiveMeshHealth union, 7 CM escalation fields on AgentTask
  • App.tsx'cogmesh' tab + CogmeshDot status indicator

Test plan

  • CM endpoint configured → health pill shows connected
  • CM unreachable → unreachable after 5 s timeout
  • No endpoint → SystemHealthBar hidden, CognitiveMeshPanel shows config message
  • .retortconfig updated → poller reloads within 30 s

Closes #2

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Added Cognitive Mesh health monitoring with real-time status and latency indicators.
    • Introduced new "Mesh" tab for viewing and managing Cognitive Mesh workflows.
    • Added system health bar displaying mesh connectivity status.
    • Configuration support through .retortconfig file.
    • Extended task tracking with workflow metadata including progress stages and fallback options.

- Add CognitiveMeshPanel: workflow cards with stage progress, health badge,
  fallback indicator, and view-result links; hidden when unconfigured
- Add SystemHealthBar: service health pills in AgentFleetPanel header;
  shows CM status (connected/degraded/unreachable/unconfigured), hidden
  when all services are unconfigured
- Add cogmesh-poller: polls CM /health every 30 s, emits cogmesh:health:updated
  via WebSocket broadcast; reloads on .retortconfig change
- Add parsers/retortconfig.ts: reads cogmesh.endpoint/secret from .retortconfig
  with ${VAR} env var substitution
- Extend protocol.ts + bridge/types.ts: CognitiveMeshHealth discriminated union,
  7 CM escalation fields on AgentTask, cogmesh:health:updated HostMessage
- Add 'cogmesh' ActivePanel + CogmeshDot status indicator in App.tsx
- Wire cogmeshHealth into useStore

Closes retort-plugins#2

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@blocksorg
Copy link
Copy Markdown

blocksorg bot commented Mar 29, 2026

Mention Blocks like a regular teammate with your question or request:

@blocks review this pull request
@blocks make the following changes ...
@blocks create an issue from what was mentioned in the following comment ...
@blocks explain the following code ...
@blocks are there any security or performance concerns?

Run @blocks /help for more information.

Workspace settings | Disable this message

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ca24d4e4-29be-4ab5-b82d-d1fe95b1eccc

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements cognitive mesh health monitoring and a UI panel to surface workflow status. The state-watcher parses .retortconfig to extract mesh endpoints, periodically polls the /health endpoint every 30 seconds, and broadcasts health updates via WebSocket. The UI integrates a new CognitiveMeshPanel, health indicators, and supporting styling.

Changes

Cohort / File(s) Summary
Cognitive Mesh Health Polling
packages/state-watcher/src/cogmesh-poller.ts, packages/state-watcher/src/parsers/retortconfig.ts
New polling module that reads .retortconfig for mesh endpoint/secret, checks health every 30s with 5s timeout and Bearer auth, emits health states (connected, degraded, unreachable, unconfigured) with latency tracking, and exposes reload() and stop() controls.
State-Watcher Integration
packages/state-watcher/src/index.ts
Instantiates cogmesh poller, broadcasts cogmesh:health:updated messages, triggers reload() when pending changes include generic config, and calls stop() on shutdown.
Protocol & Configuration Extensions
packages/state-watcher/src/protocol.ts, packages/state-watcher/src/parsers/teams.ts
Added CognitiveMeshHealth union type and cogmesh:health:updated message to protocol; extended AgentTask with workflow metadata fields (escalatedTo, workflowId, workflowName, workflowStage, workflowTotalStages, fallbackCli, resultPath); added repo name detection via .agentkit-repo marker and multi-path YAML fallback in teams parser.
UI Type System & Store
packages/ui/src/bridge/types.ts, packages/ui/src/bridge/useStore.ts
Mirrored protocol extensions to UI layer; added cogmeshHealth state field, extended ActivePanel union with 'cogmesh', and wired cogmesh:health:updated message handler.
UI Components
packages/ui/src/components/SystemHealthBar.tsx, packages/ui/src/panels/CognitiveMeshPanel.tsx, packages/ui/src/panels/AgentFleetPanel.tsx
New SystemHealthBar component renders status pills for mesh health; new CognitiveMeshPanel displays active/recent workflows with stage progress, status icons, latency, fallback CLI, and result links; integrated health bar into AgentFleetPanel at top of panel.
UI Layout & App Integration
packages/ui/src/App.tsx
Added "Mesh" tab rendering CognitiveMeshPanel; introduced CogmeshDot indicator showing health status and latency tooltip in header.
Styling
packages/ui/src/styles/index.css
Added .system-health-bar and .health-pill styling (ok/warn/error/off states with color variants); added .cogmesh-* styles for panel layout, workflow cards, stage progress bars, and result links.

Sequence Diagram

sequenceDiagram
    participant UI as React UI
    participant WS as WebSocket Bridge
    participant SW as State-Watcher
    participant FS as File System
    participant CM as Cognitive Mesh API

    SW->>FS: Read .retortconfig
    FS-->>SW: endpoint + secret
    
    loop Every 30 seconds
        SW->>CM: GET /health (Bearer auth, 5s timeout)
        alt Health OK
            CM-->>SW: { ok: true, latencyMs }
            SW->>SW: Set status: connected/degraded
        else Request fails
            CM-->>SW: Timeout/Network error
            SW->>SW: Set status: unreachable
        end
        
        SW->>WS: Broadcast cogmesh:health:updated
        WS->>UI: Message received
        UI->>UI: Update cogmeshHealth state
        UI->>UI: Render CogmeshDot + SystemHealthBar
    end

    Note over SW,UI: User triggers reload (e.g., config change)
    SW->>SW: Re-parse .retortconfig
    SW->>CM: Immediate health check
    SW->>WS: Broadcast updated health
    WS->>UI: Refresh health display
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 hops through the mesh with a bound,
Polling health every thirty seconds round!
Whiskers twitch as the workflows flow,
Status and stages in panels aglow—
Now developers see their tasks from the ground! 📡✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(ui): add CognitiveMeshPanel, SystemHealthBar, and cogmesh-poller' accurately summarizes the three major components added in the pull request.
Linked Issues check ✅ Passed The pull request implements all major requirements from issue #2: CognitiveMeshPanel with workflow cards, health badge, stage progress, fallback indicators, and view-result links; SystemHealthBar for service health; cogmesh-poller polling CM /health every 30s; protocol extensions for CognitiveMeshHealth; and proper panel integration.
Out of Scope Changes check ✅ Passed All changes are within scope of issue #2 requirements: state-watcher integration, protocol/bridge type extensions, UI panel and components, health monitoring, and .retortconfig parsing.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cognitive-mesh-panel

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (5)
packages/state-watcher/src/cogmesh-poller.ts (2)

46-49: Rapid reload() calls can cause overlapping health checks.

If .retortconfig changes trigger multiple rapid reload() calls (before debouncing in index.ts), several checkHealth() invocations could run concurrently, potentially emitting out-of-order health updates.

Consider tracking an in-flight request to skip or abort previous checks:

🔧 Option: guard against concurrent checks
+  let abortController: AbortController | null = null
+
   async function checkHealth(): Promise<void> {
+    abortController?.abort()
+    abortController = new AbortController()
+
     if (!config.endpoint) {
       onHealth({ status: 'unconfigured' })
       return
     }

     const url = `${config.endpoint.replace(/\/$/, '')}/health`
     const headers: Record<string, string> = {}
     if (config.secret) headers['Authorization'] = `Bearer ${config.secret}`

     const start = Date.now()
     try {
       const res = await fetch(url, {
         headers,
-        signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS),
+        signal: AbortSignal.any([
+          abortController.signal,
+          AbortSignal.timeout(HEALTH_TIMEOUT_MS),
+        ]),
       })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/state-watcher/src/cogmesh-poller.ts` around lines 46 - 49, The
reload() function calls checkHealth() unconditionally which allows overlapping
health checks when reload() is invoked rapidly; modify reload() and
checkHealth() to track an in-flight check (e.g., an inFlight boolean or an
AbortController stored at module scope) so that reload() either cancels the
previous check before starting a new one or skips starting a new check while one
is running; update references to config populated by parseCogmeshConfig(root)
accordingly so the running check can read the correct config snapshot and ensure
checkHealth() respects the cancellation/guard to prevent concurrent executions
and out-of-order health updates.

51-54: stop() doesn't cancel in-flight requests.

Calling stop() clears the interval but any ongoing checkHealth() fetch will continue to completion and call onHealth() after the poller is "stopped". This is likely benign but worth noting if shutdown ordering matters.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/state-watcher/src/cogmesh-poller.ts` around lines 51 - 54, The
stop() function currently only clears the interval (timer) but does not cancel
any in-flight checkHealth() fetches, so add cancellation: introduce an
AbortController (or an inFlightController) used by checkHealth() when calling
fetch/HTTP client and store it on the poller; update stop() to call
controller.abort() and set timer = null and a stopped flag; update checkHealth()
to accept/use the AbortSignal and to skip calling onHealth() if the request was
aborted (catch AbortError or check the stopped flag) so no callbacks run after
stop() is called. Ensure you reference and modify stop(), checkHealth(), and
onHealth() accordingly.
packages/state-watcher/src/parsers/retortconfig.ts (2)

33-36: Static analysis flags ReDoS risk — low practical concern given controlled keys.

The key parameter is always hardcoded ('endpoint', 'secret') from within parseCogmeshConfig, so the regex pattern is effectively static. However, if this helper were reused with untrusted keys in the future, the dynamic regex construction could become a vulnerability.

Consider extracting dedicated functions or using a key whitelist if the parser expands.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/state-watcher/src/parsers/retortconfig.ts` around lines 33 - 36, The
extractScalar function builds a dynamic RegExp from the provided key which poses
a ReDoS risk if untrusted keys are ever passed; restrict usage by either only
accepting the known keys used by parseCogmeshConfig ('endpoint' and 'secret') or
replace the dynamic-regex approach with a safe lookup: precompute/compile
separate regexes for those known keys (or whitelist and escape the key before
building the RegExp) and update extractScalar (or introduce
extractEndpoint/extractSecret) accordingly so no arbitrary/unescaped key is
interpolated into a RegExp at runtime.

38-41: Unresolved env vars are preserved — verify this is intended.

When ${VAR} references an undefined environment variable, the placeholder is kept verbatim (e.g., the endpoint becomes "https://${MISSING}/api"). This could cause confusing runtime errors from the poller trying to fetch a malformed URL.

Consider logging a warning or returning null when env substitution fails.

🔧 Option: warn on unresolved vars
 function resolveEnv(value: string | null): string | null {
   if (!value) return null
-  return value.replace(/\$\{([^}]+)\}/g, (_, name: string) => process.env[name] ?? `\${${name}}`)
+  return value.replace(/\$\{([^}]+)\}/g, (match, name: string) => {
+    const resolved = process.env[name]
+    if (resolved === undefined) {
+      process.stderr.write(`[retortconfig] Warning: env var ${name} is not set\n`)
+    }
+    return resolved ?? match
+  })
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/state-watcher/src/parsers/retortconfig.ts` around lines 38 - 41, The
resolveEnv function currently preserves unresolved ${VAR} placeholders which can
produce malformed URLs at runtime; update resolveEnv to detect any missing env
names during replacement (e.g., collect names where process.env[name] is
undefined) and if any are missing either log a warning (including the original
input and the missing variable names) and return null, or at minimum return null
when any placeholder remains unresolved; implement this in resolveEnv so callers
(like the poller) get a null instead of a broken string and can handle the error
upstream.
packages/state-watcher/src/parsers/teams.ts (1)

21-29: Potential issue with untrusted marker file content.

If .agentkit-repo contains path separators, leading/trailing slashes, or special characters, the resulting overlay path could resolve unexpectedly (e.g., path traversal or broken paths). Consider sanitizing the marker content.

🛡️ Proposed sanitization
 function detectRepoName(root: string): string {
   try {
     const markerPath = path.join(root, '.agentkit-repo')
     if (fs.existsSync(markerPath)) {
-      return fs.readFileSync(markerPath, 'utf-8').trim()
+      const raw = fs.readFileSync(markerPath, 'utf-8').trim()
+      // Sanitize: strip path separators and use only the basename portion
+      const sanitized = path.basename(raw.replace(/[\\/]/g, '-'))
+      return sanitized || path.basename(root)
     }
   } catch { /* ignore */ }
   return path.basename(root)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/state-watcher/src/parsers/teams.ts` around lines 21 - 29, The
detectRepoName function reads untrusted .agentkit-repo content and returns it
raw which can produce unsafe or unexpected paths; update detectRepoName to
sanitize the marker content read from markerPath: trim it, reject or strip path
separators and any path traversal tokens (e.g., ".."), and restrict characters
(e.g., allow only alphanumerics, dots, underscores, hyphens) or otherwise take
path.basename() of the content; if the sanitized result is empty or invalid,
fall back to path.basename(root) to avoid creating unsafe overlay paths. Ensure
these checks are applied to the value returned by fs.readFileSync before
returning from detectRepoName.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/state-watcher/src/cogmesh-poller.ts`:
- Around line 31-35: The project uses AbortSignal.timeout and global fetch (seen
in cogmesh-poller.ts with AbortSignal.timeout(HEALTH_TIMEOUT_MS) and fetch), so
add an explicit engines.node field to both the root package.json and
packages/state-watcher/package.json to require Node.js >=18.0.0 (or a higher
minimum you prefer); update each package.json's "engines" section to include
"node": ">=18.0.0" so CI and consumers are warned/enforced about the Node
version compatibility.

In `@packages/ui/src/panels/CognitiveMeshPanel.tsx`:
- Around line 143-149: The formatAge function can produce "NaNh ago" for
malformed ISO strings; update formatAge to validate the parsed timestamp (e.g.,
const ts = new Date(iso).getTime()) and check Number.isFinite(ts) or !isNaN(ts)
before computing diffMs; if the timestamp is invalid, return a safe fallback
like 'unknown' or 'just now' to avoid leaking bad state into the UI. Ensure all
existing branches (mins < 1, mins < 60, hours) remain unchanged once the parsed
timestamp is validated.
- Around line 20-25: In StageBar, the calculated pct can go out of 0–100 when
upstream provides unexpected stage/total values; clamp the computed percent to
the 0–100 range (e.g., compute raw percent when total>0 then apply Math.max(0,
Math.min(100, rawPct))) so the inline style width never becomes invalid and
handle total<=0 by using 0.

In `@packages/ui/src/styles/index.css`:
- Line 330: The CSS declaration "border: 1px solid currentColor" uses camelCase
and violates the Stylelint value-keyword-case rule; update the declaration by
replacing the token currentColor with lowercase currentcolor (i.e., change the
value in the "border: 1px solid currentColor" rule), then re-run stylelint to
confirm the violation is resolved.

---

Nitpick comments:
In `@packages/state-watcher/src/cogmesh-poller.ts`:
- Around line 46-49: The reload() function calls checkHealth() unconditionally
which allows overlapping health checks when reload() is invoked rapidly; modify
reload() and checkHealth() to track an in-flight check (e.g., an inFlight
boolean or an AbortController stored at module scope) so that reload() either
cancels the previous check before starting a new one or skips starting a new
check while one is running; update references to config populated by
parseCogmeshConfig(root) accordingly so the running check can read the correct
config snapshot and ensure checkHealth() respects the cancellation/guard to
prevent concurrent executions and out-of-order health updates.
- Around line 51-54: The stop() function currently only clears the interval
(timer) but does not cancel any in-flight checkHealth() fetches, so add
cancellation: introduce an AbortController (or an inFlightController) used by
checkHealth() when calling fetch/HTTP client and store it on the poller; update
stop() to call controller.abort() and set timer = null and a stopped flag;
update checkHealth() to accept/use the AbortSignal and to skip calling
onHealth() if the request was aborted (catch AbortError or check the stopped
flag) so no callbacks run after stop() is called. Ensure you reference and
modify stop(), checkHealth(), and onHealth() accordingly.

In `@packages/state-watcher/src/parsers/retortconfig.ts`:
- Around line 33-36: The extractScalar function builds a dynamic RegExp from the
provided key which poses a ReDoS risk if untrusted keys are ever passed;
restrict usage by either only accepting the known keys used by
parseCogmeshConfig ('endpoint' and 'secret') or replace the dynamic-regex
approach with a safe lookup: precompute/compile separate regexes for those known
keys (or whitelist and escape the key before building the RegExp) and update
extractScalar (or introduce extractEndpoint/extractSecret) accordingly so no
arbitrary/unescaped key is interpolated into a RegExp at runtime.
- Around line 38-41: The resolveEnv function currently preserves unresolved
${VAR} placeholders which can produce malformed URLs at runtime; update
resolveEnv to detect any missing env names during replacement (e.g., collect
names where process.env[name] is undefined) and if any are missing either log a
warning (including the original input and the missing variable names) and return
null, or at minimum return null when any placeholder remains unresolved;
implement this in resolveEnv so callers (like the poller) get a null instead of
a broken string and can handle the error upstream.

In `@packages/state-watcher/src/parsers/teams.ts`:
- Around line 21-29: The detectRepoName function reads untrusted .agentkit-repo
content and returns it raw which can produce unsafe or unexpected paths; update
detectRepoName to sanitize the marker content read from markerPath: trim it,
reject or strip path separators and any path traversal tokens (e.g., ".."), and
restrict characters (e.g., allow only alphanumerics, dots, underscores, hyphens)
or otherwise take path.basename() of the content; if the sanitized result is
empty or invalid, fall back to path.basename(root) to avoid creating unsafe
overlay paths. Ensure these checks are applied to the value returned by
fs.readFileSync before returning from detectRepoName.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8019986-a916-4ad4-93e7-8ee0634107f4

📥 Commits

Reviewing files that changed from the base of the PR and between 7304461 and ef27e09.

📒 Files selected for processing (12)
  • packages/state-watcher/src/cogmesh-poller.ts
  • packages/state-watcher/src/index.ts
  • packages/state-watcher/src/parsers/retortconfig.ts
  • packages/state-watcher/src/parsers/teams.ts
  • packages/state-watcher/src/protocol.ts
  • packages/ui/src/App.tsx
  • packages/ui/src/bridge/types.ts
  • packages/ui/src/bridge/useStore.ts
  • packages/ui/src/components/SystemHealthBar.tsx
  • packages/ui/src/panels/AgentFleetPanel.tsx
  • packages/ui/src/panels/CognitiveMeshPanel.tsx
  • packages/ui/src/styles/index.css

Comment on lines +31 to +35
try {
const res = await fetch(url, {
headers,
signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS),
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for Node.js version requirements in package.json or similar configs
fd -t f 'package.json' --exec cat {} \; | jq -r '.engines.node // empty' 2>/dev/null || echo "No engines.node found"

Repository: phoenixvc/retort-plugins

Length of output: 50


🏁 Script executed:

# Check if package.json exists and examine its full contents
cd packages/state-watcher && cat package.json || echo "No package.json found in state-watcher"

Repository: phoenixvc/retort-plugins

Length of output: 631


🏁 Script executed:

# Also check project root package.json
cat package.json 2>/dev/null || echo "No package.json in root"

Repository: phoenixvc/retort-plugins

Length of output: 885


🏁 Script executed:

# Check for any Node.js version constraint files
fd -t f '\.nvmrc|\.node-version|\.tool-versions' -x cat {}

Repository: phoenixvc/retort-plugins

Length of output: 50


Add explicit engines.node field to package.json to clarify minimum Node.js version.

The code uses AbortSignal.timeout() (requires Node.js ≥17.3) and global fetch (requires Node.js ≥18). While the project's @types/node dependency is pinned to ^20.0.0, suggesting Node.js 20+, there is no explicit engines.node field in either packages/state-watcher/package.json or the root package.json. Add "engines": { "node": ">=18.0.0" } (or higher) to both package.json files to enforce this requirement and prevent accidental use with incompatible Node versions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/state-watcher/src/cogmesh-poller.ts` around lines 31 - 35, The
project uses AbortSignal.timeout and global fetch (seen in cogmesh-poller.ts
with AbortSignal.timeout(HEALTH_TIMEOUT_MS) and fetch), so add an explicit
engines.node field to both the root package.json and
packages/state-watcher/package.json to require Node.js >=18.0.0 (or a higher
minimum you prefer); update each package.json's "engines" section to include
"node": ">=18.0.0" so CI and consumers are warned/enforced about the Node
version compatibility.

Comment on lines +20 to +25
function StageBar({ stage, total }: { stage: number; total: number }) {
const pct = total > 0 ? Math.round((stage / total) * 100) : 0
return (
<div className="cm-stage-bar" title={`Stage ${stage} of ${total}`}>
<div className="cm-stage-fill" style={{ width: `${pct}%` }} />
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clamp progress percent to avoid invalid/out-of-range widths.

If upstream sends unexpected values (e.g., stage > total), Line 24 can produce widths above 100%.

🔧 Proposed fix
 function StageBar({ stage, total }: { stage: number; total: number }) {
-  const pct = total > 0 ? Math.round((stage / total) * 100) : 0
+  const raw = total > 0 ? (stage / total) * 100 : 0
+  const pct = Math.max(0, Math.min(100, Math.round(raw)))
   return (
     <div className="cm-stage-bar" title={`Stage ${stage} of ${total}`}>
       <div className="cm-stage-fill" style={{ width: `${pct}%` }} />
     </div>
   )
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function StageBar({ stage, total }: { stage: number; total: number }) {
const pct = total > 0 ? Math.round((stage / total) * 100) : 0
return (
<div className="cm-stage-bar" title={`Stage ${stage} of ${total}`}>
<div className="cm-stage-fill" style={{ width: `${pct}%` }} />
</div>
function StageBar({ stage, total }: { stage: number; total: number }) {
const raw = total > 0 ? (stage / total) * 100 : 0
const pct = Math.max(0, Math.min(100, Math.round(raw)))
return (
<div className="cm-stage-bar" title={`Stage ${stage} of ${total}`}>
<div className="cm-stage-fill" style={{ width: `${pct}%` }} />
</div>
)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/panels/CognitiveMeshPanel.tsx` around lines 20 - 25, In
StageBar, the calculated pct can go out of 0–100 when upstream provides
unexpected stage/total values; clamp the computed percent to the 0–100 range
(e.g., compute raw percent when total>0 then apply Math.max(0, Math.min(100,
rawPct))) so the inline style width never becomes invalid and handle total<=0 by
using 0.

Comment on lines +143 to +149
function formatAge(iso: string): string {
const diffMs = Date.now() - new Date(iso).getTime()
const mins = Math.floor(diffMs / 60_000)
if (mins < 1) return 'just now'
if (mins < 60) return `${mins}m ago`
return `${Math.floor(mins / 60)}h ago`
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Guard invalid timestamps in formatAge.

If iso is malformed, Line 148 can render NaNh ago, which leaks bad state into UI text.

🔧 Proposed fix
 function formatAge(iso: string): string {
-  const diffMs = Date.now() - new Date(iso).getTime()
+  const ts = new Date(iso).getTime()
+  if (!Number.isFinite(ts)) return 'unknown'
+  const diffMs = Math.max(0, Date.now() - ts)
   const mins = Math.floor(diffMs / 60_000)
   if (mins < 1)  return 'just now'
   if (mins < 60) return `${mins}m ago`
   return `${Math.floor(mins / 60)}h ago`
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function formatAge(iso: string): string {
const diffMs = Date.now() - new Date(iso).getTime()
const mins = Math.floor(diffMs / 60_000)
if (mins < 1) return 'just now'
if (mins < 60) return `${mins}m ago`
return `${Math.floor(mins / 60)}h ago`
}
function formatAge(iso: string): string {
const ts = new Date(iso).getTime()
if (!Number.isFinite(ts)) return 'unknown'
const diffMs = Math.max(0, Date.now() - ts)
const mins = Math.floor(diffMs / 60_000)
if (mins < 1) return 'just now'
if (mins < 60) return `${mins}m ago`
return `${Math.floor(mins / 60)}h ago`
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/panels/CognitiveMeshPanel.tsx` around lines 143 - 149, The
formatAge function can produce "NaNh ago" for malformed ISO strings; update
formatAge to validate the parsed timestamp (e.g., const ts = new
Date(iso).getTime()) and check Number.isFinite(ts) or !isNaN(ts) before
computing diffMs; if the timestamp is invalid, return a safe fallback like
'unknown' or 'just now' to avoid leaking bad state into the UI. Ensure all
existing branches (mins < 1, mins < 60, hours) remain unchanged once the parsed
timestamp is validated.

font-size: 11px;
padding: 1px 7px;
border-radius: 10px;
border: 1px solid currentColor;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify occurrences of currentColor/currentcolor in the updated stylesheet
rg -n 'currentColor|currentcolor' packages/ui/src/styles/index.css

Repository: phoenixvc/retort-plugins

Length of output: 105


🏁 Script executed:

#!/bin/bash
# Search for stylelint configuration files
find . -maxdepth 3 -type f \( -name '.stylelintrc*' -o -name '.stylelintignore' \) 2>/dev/null | head -20
# Also check package.json for stylelint config
if [ -f package.json ]; then
  cat package.json | grep -A 10 '"stylelint"'
fi

Repository: phoenixvc/retort-plugins

Length of output: 87


🌐 Web query:

CSS currentColor keyword case sensitivity stylelint value-keyword-case rule

💡 Result:

The CSS currentColor keyword is case-insensitive in all browsers. CSS keywords are ASCII case-insensitive per CSS2/CSS3 specs, so currentColor, currentcolor, and CURRENTCOLOR all work identically, resolving to the element's color property value. This is confirmed across Chrome, Firefox, Safari, and Edge devtools, which accept any casing despite varying autocomplete preferences. The stylelint value-keyword-case rule, when set to "lower" (default in many configs), flags camelCase currentColor as invalid and suggests currentcolor, aligning with CSS Color Level 4 spec notation (lowercase currentcolor). However, it previously treated SVG-originated keywords like currentColor as exceptions requiring camelCase. To allow camelCase currentColor with "lower" option, enable the camelCaseSvgKeywords: true secondary option: { "rules": { "value-keyword-case": ["lower", { "camelCaseSvgKeywords": true }] } } This permits camelCase for SVG keywords (e.g., currentColor, optimizeQuality) while enforcing lowercase for standard keywords (e.g., block, flex). Without it, use "currentcolor" or ignoreKeywords: ["currentColor"] / ignoreProperties: ["color"] as workarounds. camelCaseSvgKeywords defaults to false, prioritizing spec's lowercase preference. All browser support remains unchanged regardless of casing used.

Citations:


🏁 Script executed:

#!/bin/bash
cat .stylelintrc.json

Repository: phoenixvc/retort-plugins

Length of output: 1074


Change currentColor to lowercase currentcolor to comply with Stylelint's default value-keyword-case rule.

The repository's Stylelint configuration extends stylelint-config-standard-scss without overriding the value-keyword-case rule, which defaults to enforcing lowercase keywords. Line 330 contains currentColor in camelCase, which triggers this violation.

🔧 Proposed fix
   font-size: 11px;
   padding: 1px 7px;
   border-radius: 10px;
-  border: 1px solid currentColor;
+  border: 1px solid currentcolor;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
border: 1px solid currentColor;
font-size: 11px;
padding: 1px 7px;
border-radius: 10px;
border: 1px solid currentcolor;
}
🧰 Tools
🪛 Stylelint (17.5.0)

[error] 330-330: Expected "currentColor" to be "currentcolor" (value-keyword-case)

(value-keyword-case)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui/src/styles/index.css` at line 330, The CSS declaration "border:
1px solid currentColor" uses camelCase and violates the Stylelint
value-keyword-case rule; update the declaration by replacing the token
currentColor with lowercase currentcolor (i.e., change the value in the "border:
1px solid currentColor" rule), then re-run stylelint to confirm the violation is
resolved.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@JustAGhosT JustAGhosT merged commit 2792cc4 into main Mar 31, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: CognitiveMeshPanel — surface CM workflow status and escalations in IDE plugin

1 participant