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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apps/microbridge-ui/src/assets/integrations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Integration icons

PNG marks used in Settings → Integrations. Extracted from each host’s installed
macOS app icon for identification in the Microbridge UI.

All trademarks and logos remain the property of their respective owners.
Microbridge does not claim ownership of these marks.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 53 additions & 11 deletions apps/microbridge-ui/src/components/IntegrationCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import {

function TileFace({
name,
iconSrc,
diagnostic,
light,
label,
theme,
}: {
name: string;
iconSrc?: string;
diagnostic: string;
light: TrafficLight;
label: string;
Expand All @@ -21,25 +23,45 @@ function TileFace({
const colors = TRAFFIC_COLORS[light];
return (
<>
<span
className="inline-block h-2.5 w-2.5 shrink-0 rounded-full"
style={{ backgroundColor: colors.dot }}
aria-hidden
/>
<div className="flex w-full items-start justify-between gap-1">
{iconSrc ? (
<img
src={iconSrc}
alt=""
width={28}
height={28}
className="h-7 w-7 shrink-0 rounded-[7px]"
draggable={false}
/>
) : (
<span
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-[7px] text-[11px] font-semibold"
style={{ backgroundColor: theme.hoverBg, color: theme.textSecondary }}
aria-hidden
>
{name.slice(0, 1)}
</span>
)}
<span
className="mt-0.5 inline-block h-2 w-2 shrink-0 rounded-full"
style={{ backgroundColor: colors.dot }}
aria-hidden
/>
</div>
<div className="min-w-0 w-full">
<div className="truncate text-[12.5px] font-semibold leading-tight">
<div className="truncate text-[11px] font-semibold leading-tight">
{name}
</div>
<div
className="mt-1 truncate text-[10px] font-medium"
className="mt-0.5 truncate text-[9.5px] font-medium leading-tight"
style={{ color: colors.fg }}
>
{label}
</div>
</div>
<div
role="tooltip"
className="pointer-events-none absolute bottom-[calc(100%+6px)] left-1/2 z-20 w-[min(220px,70vw)] -translate-x-1/2 rounded-lg px-2.5 py-2 text-[10.5px] leading-snug opacity-0 shadow-lg transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100"
className="pointer-events-none absolute bottom-[calc(100%+6px)] left-1/2 z-20 w-[min(200px,70vw)] -translate-x-1/2 rounded-lg px-2.5 py-2 text-[10.5px] leading-snug opacity-0 shadow-lg transition-opacity group-hover:opacity-100 group-focus-visible:opacity-100"
style={{
backgroundColor: theme.name === "dark" ? "#1C1C1E" : "#FFFFFF",
border: `1px solid ${theme.hairline}`,
Expand All @@ -54,6 +76,7 @@ function TileFace({

export function IntegrationCard({
name,
iconSrc,
diagnostic,
light,
label,
Expand All @@ -63,6 +86,7 @@ export function IntegrationCard({
onSelect,
}: {
name: string;
iconSrc?: string;
diagnostic: string;
light: TrafficLight;
label: string;
Expand All @@ -82,13 +106,17 @@ export function IntegrationCard({
const face = (
<TileFace
name={name}
iconSrc={iconSrc}
diagnostic={diagnostic}
light={light}
label={label}
theme={theme}
/>
);

const className =
"group relative flex h-[72px] w-full flex-col items-stretch justify-between rounded-xl px-2 py-1.5 text-left transition-colors";

return (
<li className="relative list-none">
{onSelect ? (
Expand All @@ -98,7 +126,7 @@ export function IntegrationCard({
title={diagnostic}
aria-label={`${name}: ${label}. ${diagnostic}`}
aria-expanded={expandable ? expanded : undefined}
className="group relative flex aspect-square w-full flex-col items-start justify-between rounded-2xl p-3 text-left transition-colors"
className={className}
style={shellStyle}
>
{face}
Expand All @@ -107,7 +135,7 @@ export function IntegrationCard({
<div
title={diagnostic}
aria-label={`${name}: ${label}. ${diagnostic}`}
className="group relative flex aspect-square w-full flex-col items-start justify-between rounded-2xl p-3"
className={className}
style={shellStyle}
>
{face}
Expand All @@ -119,11 +147,13 @@ export function IntegrationCard({

export function IntegrationDetail({
name,
iconSrc,
diagnostic,
theme,
children,
}: {
name: string;
iconSrc?: string;
diagnostic: string;
theme: ThemeTokens;
children?: ReactNode;
Expand All @@ -136,7 +166,19 @@ export function IntegrationDetail({
border: `1px solid ${theme.hairline}`,
}}
>
<div className="text-[12.5px] font-semibold">{name}</div>
<div className="flex items-center gap-2">
{iconSrc ? (
<img
src={iconSrc}
alt=""
width={22}
height={22}
className="h-[22px] w-[22px] rounded-[6px]"
draggable={false}
/>
) : null}
<div className="text-[12.5px] font-semibold">{name}</div>
</div>
<div className="mt-1 text-[11px]" style={{ color: theme.textSecondary }}>
{diagnostic}
</div>
Expand Down
29 changes: 29 additions & 0 deletions apps/microbridge-ui/src/lib/integrationIcons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import chatgpt from "../assets/integrations/chatgpt.png";
import claude from "../assets/integrations/claude.png";
import cnvs from "../assets/integrations/cnvs.png";
import codex from "../assets/integrations/codex.png";
import conductor from "../assets/integrations/conductor.png";
import cursor from "../assets/integrations/cursor.png";
import factory from "../assets/integrations/factory.png";
import opencode from "../assets/integrations/opencode.png";
import synara from "../assets/integrations/synara.png";
import t3code from "../assets/integrations/t3code.png";

/** App icons keyed by daemon adapter id. */
export const INTEGRATION_ICONS: Record<string, string> = {
chatgpt,
claude,
claude_desktop: claude,
cnvs,
codex,
conductor,
cursor,
factory,
opencode,
synara,
t3code,
};
Comment on lines +12 to +25

export function integrationIcon(adapterId: string): string | undefined {
return INTEGRATION_ICONS[adapterId];
}
11 changes: 7 additions & 4 deletions apps/microbridge-ui/src/surfaces/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
integrationView,
isHostAttributed,
} from "../lib/hosts";
import { integrationIcon } from "../lib/integrationIcons";

const LIGHTING_STATES: { id: keyof StateColors; label: string }[] = [
{ id: "idle", label: "Idle" },
Expand Down Expand Up @@ -641,9 +642,9 @@ export function Settings({
<section>
<h1 className="text-[18px] font-semibold">Integrations</h1>
<p className="mt-1 text-[12.5px]" style={{ color: t.textSecondary }}>
Each app is a tile. Green / yellow / red is always visible; hover
for detail. Click Cursor, Factory, T3 Code, or OpenCode to enable
or repair.
Compact tiles with each app&apos;s icon. Status color is always
visible; hover for detail. Click Cursor, Factory, T3 Code, or
OpenCode to enable or repair.
</p>
<p className="mt-2 text-[11px]" style={{ color: t.textMuted }}>
Synara and the desktop apps share Claude/Codex journals — no
Expand All @@ -661,11 +662,12 @@ export function Settings({
<span className="h-2 w-2 rounded-full" style={{ backgroundColor: group.items.length ? TRAFFIC_COLORS[group.light].dot : t.textMuted }} />
{group.label} · {group.items.length}
</div>
<ul className="mt-2 grid grid-cols-2 gap-2 sm:grid-cols-3">
<ul className="mt-2 grid grid-cols-3 gap-1.5 sm:grid-cols-4 lg:grid-cols-5">
{group.items.map(({ adapter, view, actionable }) => (
<IntegrationCard
key={adapter.id}
name={adapter.display_name}
iconSrc={integrationIcon(adapter.id)}
diagnostic={view.diagnostic}
light={view.light}
label={view.label}
Expand All @@ -688,6 +690,7 @@ export function Settings({
{selected && (
<IntegrationDetail
name={selected.adapter.display_name}
iconSrc={integrationIcon(selected.adapter.id)}
diagnostic={selected.view.diagnostic}
theme={t}
>
Expand Down
4 changes: 3 additions & 1 deletion apps/microbridge-ui/src/surfaces/surfaces.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ describe("Settings", () => {
expect(html).toContain("Lifecycle is connected");
expect(html).toContain("Live state");
expect(html).toContain("Integrations");
expect(html).toContain("grid-cols-2");
expect(html).toContain("grid-cols-3");
expect(html).toContain("integrations/");
// Synara waits (yellow) with no sessions; CNVS stays connected.
expect(html).toContain("Connected · 1");
expect(html).toContain("Not connected · 2");
Expand All @@ -147,6 +148,7 @@ describe("Settings", () => {
// Cursor is auto-selected as the first actionable not-connected tile.
expect(html).toContain("Repair bundled integration");
expect(html).toContain("hover for detail");
expect(html).toContain('width="28"');
Comment on lines 149 to +151
expect(html).not.toContain("Install managed plugin");
expect(html).not.toContain("scaffold only");
expect(html).not.toContain("not production");
Expand Down
11 changes: 6 additions & 5 deletions docs/design/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,12 @@ Keyboard setup. Four sections in a left rail:
- **Agent Keys** — live "six keys, six threads" view, key source, deck focus
mode (auto/pinned), app priority order, approvals-interrupt toggle (policy
only — no live approve UI)
- **Integrations** — compact square tile grid (2–3 columns), grouped by
connected / not connected. Each tile always shows name + traffic-light status;
hover reveals the diagnostic. Click Cursor / Factory / T3 / OpenCode to open a
detail strip for enable / repair / pair. Host-attributed apps (Synara,
ChatGPT, Claude Desktop, Conductor) are status + hover only
- **Integrations** — dense icon tile grid (3–5 columns, ~72px tall), grouped by
connected / not connected. Each tile shows the host app icon, name, and
traffic-light status; hover reveals the diagnostic. Click Cursor / Factory /
T3 / OpenCode to open a detail strip for enable / repair / pair.
Host-attributed apps (Synara, ChatGPT, Claude Desktop, Conductor) are status +
hover only
- **Device** — Appearance (System/Light/Dark), Lighting (Codex defaults +
Phosphor preset + reset), brightness, LED test, sleep timer (default 3 min),
firmware, zero-network note
Expand Down
Loading