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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion apps/microbridge-ui/src/components/IntegrationCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,13 @@ export const IntegrationDetail = forwardRef<
iconSrc?: string;
diagnostic: string;
theme: ThemeTokens;
guidance?: { title: string; steps: string[] } | null;
children?: ReactNode;
}
>(function IntegrationDetail({ name, iconSrc, diagnostic, theme, children }, ref) {
>(function IntegrationDetail(
{ name, iconSrc, diagnostic, theme, guidance, children },
ref,
) {
return (
<div
ref={ref}
Expand All @@ -175,6 +179,22 @@ export const IntegrationDetail = forwardRef<
<div className="mt-1 text-[11px]" style={{ color: theme.textSecondary }}>
{diagnostic}
</div>
{guidance && guidance.steps.length > 0 && (
<div
className="mt-2 rounded-lg px-2.5 py-2 text-[11px] leading-snug"
style={{
backgroundColor: TRAFFIC_COLORS.yellow.bg,
color: TRAFFIC_COLORS.yellow.fg,
}}
>
<div className="font-medium">{guidance.title}</div>
<ol className="mt-1.5 list-decimal space-y-1 pl-4">
{guidance.steps.map((step) => (
<li key={step}>{step}</li>
))}
</ol>
Comment on lines +191 to +195
</div>
)}
{children}
</div>
);
Expand Down
167 changes: 167 additions & 0 deletions apps/microbridge-ui/src/components/MeshBackground.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { useEffect, useRef } from "react";

type Node = { x: number; y: number };

/** Stable low-density lattice in normalized 0–1 space. */
const NODES: Node[] = (() => {
const nodes: Node[] = [];
const cols = 6;
const rows = 5;
for (let row = 0; row < rows; row += 1) {
for (let col = 0; col < cols; col += 1) {
const jitterX = ((row * 17 + col * 31) % 7) / 100 - 0.03;
const jitterY = ((col * 13 + row * 23) % 7) / 100 - 0.03;
nodes.push({
x: (col + 0.5) / cols + jitterX,
y: (row + 0.5) / rows + jitterY,
});
}
}
return nodes;
})();

const EDGE_DIST = 0.22;
const INFLUENCE_PX = 120;

/**
* Subtle mouse-reactive mesh behind Settings chrome.
* pointer-events-none — never blocks sidebar or tile clicks.
*/
export function MeshBackground({
dark,
active = true,
}: {
dark: boolean;
active?: boolean;
}) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const mouseRef = useRef<{ x: number; y: number } | null>(null);
const rafRef = useRef<number>(0);

useEffect(() => {
const canvas = canvasRef.current;
if (!canvas || !active) return;

const ctx = canvas.getContext("2d");
if (!ctx) return;

const parent = canvas.parentElement;
if (!parent) return;

const baseAlpha = dark ? 0.055 : 0.07;
const hotAlpha = dark ? 0.18 : 0.22;
const stroke = dark ? "245,245,244" : "13,13,13";

const resize = () => {
const rect = parent.getBoundingClientRect();
const dpr = Math.min(window.devicePixelRatio || 1, 2);
canvas.width = Math.max(1, Math.floor(rect.width * dpr));
canvas.height = Math.max(1, Math.floor(rect.height * dpr));
canvas.style.width = `${rect.width}px`;
canvas.style.height = `${rect.height}px`;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
};
Comment on lines +55 to +63

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Cache the canvas rect instead of recomputing it on every mousemove.

onMove calls canvas.getBoundingClientRect() on every mousemove event, which forces a synchronous layout read on a very hot path. Since the canvas is absolutely positioned to inset-0 of parent, its rect only actually changes on resize — resize() already has the up-to-date rect. Cache it in a ref there and reuse it in onMove.

⚡ Proposed fix
+    const rectRef = { current: canvas.getBoundingClientRect() };
+
     const resize = () => {
       const rect = parent.getBoundingClientRect();
       const dpr = Math.min(window.devicePixelRatio || 1, 2);
       canvas.width = Math.max(1, Math.floor(rect.width * dpr));
       canvas.height = Math.max(1, Math.floor(rect.height * dpr));
       canvas.style.width = `${rect.width}px`;
       canvas.style.height = `${rect.height}px`;
       ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+      rectRef.current = canvas.getBoundingClientRect();
     };

     ...

     const onMove = (event: MouseEvent) => {
-      const rect = canvas.getBoundingClientRect();
       mouseRef.current = {
-        x: event.clientX - rect.left,
-        y: event.clientY - rect.top,
+        x: event.clientX - rectRef.current.left,
+        y: event.clientY - rectRef.current.top,
       };
     };

Also applies to: 69-81

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/microbridge-ui/src/components/MeshBackground.tsx` around lines 55 - 63,
Cache the canvas bounding rectangle in a ref within the resize function near the
existing rect calculation, then update onMove to reuse that cached rect instead
of calling canvas.getBoundingClientRect() for each mousemove. Preserve the
existing coordinate calculations and ensure the cache is refreshed whenever
resize runs.


resize();
const observer = new ResizeObserver(resize);
observer.observe(parent);

const onMove = (event: MouseEvent) => {
const rect = canvas.getBoundingClientRect();
mouseRef.current = {
x: event.clientX - rect.left,
y: event.clientY - rect.top,
};
};
const onLeave = () => {
mouseRef.current = null;
};

parent.addEventListener("mousemove", onMove);
parent.addEventListener("mouseleave", onLeave);

const draw = () => {
if (document.hidden) {
rafRef.current = requestAnimationFrame(draw);
return;
}

const width = canvas.clientWidth;
const height = canvas.clientHeight;
ctx.clearRect(0, 0, width, height);

const points = NODES.map((node) => ({
x: node.x * width,
y: node.y * height,
}));
const mouse = mouseRef.current;

for (let i = 0; i < points.length; i += 1) {
for (let j = i + 1; j < points.length; j += 1) {
const a = points[i]!;
const b = points[j]!;
const dx = a.x - b.x;
const dy = a.y - b.y;
const distNorm = Math.hypot(dx / width, dy / height);
if (distNorm > EDGE_DIST) continue;

let alpha = baseAlpha * (1 - distNorm / EDGE_DIST);
if (mouse) {
const midX = (a.x + b.x) / 2;
const midY = (a.y + b.y) / 2;
const near = Math.hypot(midX - mouse.x, midY - mouse.y);
if (near < INFLUENCE_PX) {
const t = 1 - near / INFLUENCE_PX;
alpha = Math.min(hotAlpha, alpha + t * (hotAlpha - baseAlpha));
}
}

ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.strokeStyle = `rgba(${stroke},${alpha.toFixed(3)})`;
ctx.lineWidth = 1;
ctx.stroke();
}
}

for (const point of points) {
let alpha = baseAlpha * 1.4;
let radius = 1.15;
if (mouse) {
const near = Math.hypot(point.x - mouse.x, point.y - mouse.y);
if (near < INFLUENCE_PX) {
const t = 1 - near / INFLUENCE_PX;
alpha = Math.min(hotAlpha + 0.05, alpha + t * 0.14);
radius = 1.15 + t * 0.9;
}
}
ctx.beginPath();
ctx.arc(point.x, point.y, radius, 0, Math.PI * 2);
ctx.fillStyle = `rgba(${stroke},${alpha.toFixed(3)})`;
ctx.fill();
}

rafRef.current = requestAnimationFrame(draw);
};

rafRef.current = requestAnimationFrame(draw);

return () => {
cancelAnimationFrame(rafRef.current);
observer.disconnect();
parent.removeEventListener("mousemove", onMove);
parent.removeEventListener("mouseleave", onLeave);
};
}, [active, dark]);

if (!active) return null;

return (
<canvas
ref={canvasRef}
aria-hidden
className="pointer-events-none absolute inset-0 z-0"
/>
);
}
17 changes: 17 additions & 0 deletions apps/microbridge-ui/src/lib/hosts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,28 @@ describe("integrationView", () => {
diagnostic: "The bundled OpenCode integration is installed.",
}),
[],
{ enabled: true },
);
expect(view.label).toBe("Setup needed");
expect(view.connectedGroup).toBe(false);
});

it("labels auto-discovered needs_setup when disabled as Detected", () => {
const view = integrationView(
adapter({
id: "cursor",
display_name: "Cursor",
kind: "community",
state: "needs_setup",
diagnostic: "Cursor detected on local machine.",
}),
[],
{ enabled: false },
);
expect(view.label).toBe("Detected — click to install");
expect(view.connectedGroup).toBe(false);
});

it("maps adapter errors to red", () => {
const view = integrationView(
adapter({
Expand Down
14 changes: 14 additions & 0 deletions apps/microbridge-ui/src/lib/hosts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,14 @@ function connectedGroupForState(state: AdapterConnectionState): boolean {
/**
* Derive the card's traffic light, label, and diagnostic from daemon adapter
* state plus live session attribution.
*
* Pass `enabled` from config when known so auto-discovered (needs_setup + not
* enabled) tiles read as “Detected — click to install” instead of “Setup needed”.
*/
export function integrationView(
adapter: AdapterStatus,
sessions: SessionStatus[],
options?: { enabled?: boolean },
): IntegrationView {
const journalApp = journalAppFor(adapter.id);
const presence = journalApp
Expand Down Expand Up @@ -157,6 +161,16 @@ export function integrationView(
};
}

// Auto-discovered on disk but not yet enabled/installed via first click.
if (adapter.state === "needs_setup" && options?.enabled === false) {
return {
light: "yellow",
label: "Detected — click to install",
diagnostic: adapter.diagnostic,
connectedGroup: false,
};
}

const light = lightForState(adapter.state);
return {
light,
Expand Down
49 changes: 48 additions & 1 deletion apps/microbridge-ui/src/lib/integrationSetup.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { describe, expect, it } from "vitest";

import { setupNextStep } from "./integrationSetup";
import {
integrationGuidance,
setupNextStep,
} from "./integrationSetup";
import { openableHostApp } from "./openHostApp";

describe("setupNextStep", () => {
Expand All @@ -13,6 +16,50 @@ describe("setupNextStep", () => {
});
});

describe("integrationGuidance", () => {
it("guides Cursor through enable → reload → events", () => {
const disabled = integrationGuidance("cursor", "disabled");
expect(disabled?.primaryAction).toBe("enable");
expect(disabled?.steps[0]).toContain("install");

const setup = integrationGuidance("cursor", "needs_setup", {
enabled: true,
});
expect(setup?.title).toContain("Cursor");
expect(setup?.steps.some((step) => step.includes("Reload"))).toBe(true);
expect(setup?.primaryAction).toBe("open_app");
});

it("treats auto-discovered needs_setup + disabled as install CTA", () => {
const detected = integrationGuidance("cursor", "needs_setup", {
enabled: false,
});
expect(detected?.title).toContain("Detected");
expect(detected?.primaryAction).toBe("enable");
});

it("covers T3 pairing, CNVS start, and idle Synara", () => {
const t3 = integrationGuidance("t3code", "needs_setup", { enabled: true });
expect(t3?.primaryAction).toBe("pair");
expect(t3?.steps.some((step) => step.includes("Network access"))).toBe(
true,
);

const cnvs = integrationGuidance("cnvs", "needs_setup");
expect(cnvs?.steps[0]).toContain("CNVS");

const synara = integrationGuidance("synara", "connected", {
label: "Idle",
});
expect(synara?.steps[0]).toContain("Idle is normal");
});

it("explains always-on Codex/Claude watchers", () => {
const codex = integrationGuidance("codex", "connected");
expect(codex?.steps[0]).toContain("always on");
});
});

describe("openableHostApp", () => {
it("names apps we can open from Integrations", () => {
expect(openableHostApp("cursor")).toBe("Cursor");
Expand Down
Loading
Loading