# Files changed · PR \#23869 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/23869/diffs)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

[feat\(desktop\): support the Work Louder Codex Micro](https://git.cafe/pingdotgg/t3code/pull/23869.md)

Version: 29

Head commit: d1e98934257ee1a1e5cf72cc0a41cebf689f8bad

Base commit: 68d5b7ac735a5e95a945084430c6b5925873cc82

Comparison base: 68d5b7ac735a5e95a945084430c6b5925873cc82

Showing up to 20 entries from this revision. If the pull request changes, reopen the first page before continuing.

## apps/desktop/package.json

Status: modified; +1; −0

```diff
@@ -28,6 +28,7 @@
     "electron-updater": "^6.8.9",
     "electron-webauthn": "catalog:",
     "ffi-rs": "1.3.2",
+    "node-hid": "3.4.0",
     "playwright-core": "1.60.0",
     "react-grab": "^0.1.32",
     "tldts": "catalog:"
```

## apps/desktop/src/codexMicro/CodexMicroDevice.ts

Status: added; +318; −0

```diff
@@ -1,0 +1,318 @@
+import * as Clock from "effect/Clock";
+import * as Deferred from "effect/Deferred";
+import * as Effect from "effect/Effect";
+import * as Fiber from "effect/Fiber";
+import * as Random from "effect/Random";
+import * as Schedule from "effect/Schedule";
+import * as Schema from "effect/Schema";
+import * as Semaphore from "effect/Semaphore";
+import type { HIDAsync } from "node-hid";
+
+import {
+  DISCONNECTED_CODEX_MICRO_STATE,
+  type CodexMicroDeviceState,
+  type CodexMicroEvent,
+  type CodexMicroLighting,
+} from "@t3tools/contracts";
+
+import {
+  CHANNEL_RPC,
+  type CodexMicroEndpoint,
+  decodeReport,
+  encodeRpcRequest,
+  findEndpoints,
+  isDeepEqual,
+  LineAssembler,
+  MAX_RPC_ID,
+  NOTIFY_METHODS,
+  parseRpcLine,
+  reconcileEndpoint,
+  RPC_METHODS,
+} from "./CodexMicroProtocol.ts";
+import { lightingFrame } from "./CodexMicroLighting.ts";
+
+export class CodexMicroError extends Schema.TaggedError<CodexMicroError>()("CodexMicroError", {
+  message: Schema.String,
+  cause: Schema.optional(Schema.Defect()),
+}) {}
+
+const MAX_CONSECUTIVE_FAILURES = 3;
+
+export interface CodexMicroDevice {
+  readonly getState: Effect.Effect<CodexMicroDeviceState>;
+  readonly setLighting: (lighting: CodexMicroLighting) => Effect.Effect<void>;
+}
+
+// A failed open waits this long before retrying, unless the topology changes.
+const RETRY_AFTER_FAILURE_MS = 10_000;
+
+// node-hid is a native addon: load it on first use so a broken prebuild can
+// only disable the Codex Micro, never stop the desktop app from starting.
+let nodeHid: Promise<typeof import("node-hid")> | null = null;
+const loadNodeHid = Effect.tryPromise({
+  try: () => (nodeHid ??= import("node-hid")),
+  catch: (cause) => new CodexMicroError({ message: "Could not load node-hid", cause }),
+});
+
+// Enumeration is async so polling never blocks the main process.
+const listEndpoints = loadNodeHid.pipe(
+  Effect.flatMap((hidModule) =>
+    Effect.tryPromise({
+      try: () => hidModule.devicesAsync(),
+      catch: (cause) => new CodexMicroError({ message: "Could not list HID devices", cause }),
+    }),
+  ),
+  Effect.map(findEndpoints),
+);
+
+/**
+ * Owns one Codex Micro for the lifetime of the scope: discovery, reconnects,
+ * serialized JSON-RPC (the firmware handles one request at a time), lighting
+ * with auto-dim, and key/joystick notifications.
+ */
+export const makeCodexMicroDevice = Effect.fn("desktop.codexMicro.makeDevice")(function* (
+  onEvent: (event: CodexMicroEvent) => void,
+) {
+  const runFork = Effect.runForkWith(yield* Effect.context<never>());
+  const rpcLock = yield* Semaphore.make(1);
+  const lines = new LineAssembler();
+  const pending = new Map<string, Deferred.Deferred<unknown, CodexMicroError>>();
+
+  let hid: HIDAsync | null = null;
+  let endpoint: CodexMicroEndpoint | null = null;
+  let state: CodexMicroDeviceState = DISCONNECTED_CODEX_MICRO_STATE;
+  let lighting: CodexMicroLighting | null = null;
+  let lightsOff = false;
+  let appliedFrame: ReturnType<typeof lightingFrame> | null = null;
+  let dimFiber: Fiber.Fiber<void> | null = null;
+
+  const setState = (next: CodexMicroDeviceState) => {
+    if (isDeepEqual(next, state)) return;
+    state = next;
+    onEvent({ type: "state", state });
+  };
+
+  const send = Effect.fn("desktop.codexMicro.send")(function* (method: string, params: unknown) {
+    const device = hid;
+    if (!device) return yield* new CodexMicroError({ message: "Codex Micro is not connected" });
+    const id = yield* Random.nextIntBetween(0, MAX_RPC_ID, { halfOpen: true });
+    const response = yield* Deferred.make<unknown, CodexMicroError>();
+    pending.set(String(id), response);
+    return yield* Effect.gen(function* () {
+      for (const report of encodeRpcRequest(method, params, id)) {
+        yield* Effect.tryPromise({
+          try: () => device.write(Buffer.from(report)),
+          catch: (cause) => new CodexMicroError({ message: `Codex Micro write failed`, cause }),
+        });
+      }
+      return yield* Deferred.await(response).pipe(
+        Effect.timeoutOrElse({
+          duration: "5 seconds",
+          orElse: () =>
+            Effect.fail(new CodexMicroError({ message: `Codex Micro ${method} timed out` })),
+        }),
+      );
+    }).pipe(Effect.ensuring(Effect.sync(() => pending.delete(String(id)))));
+  });
+
+  // A short cooldown between requests keeps the firmware's parser happy.
+  // A handle can go dead without a read error (sleep/wake, a dropped Bluetooth
+  // link). Treat a run of failed requests as a disconnect so discovery reopens it.
+  let consecutiveFailures = 0;
+  const call = (method: string, params: unknown) =>
+    rpcLock.withPermits(1)(
+      send(method, params).pipe(
+        Effect.tap(() => Effect.sync(() => (consecutiveFailures = 0))),
+        Effect.tapError(() =>
+          Effect.suspend(() =>
+            ++consecutiveFailures >= MAX_CONSECUTIVE_FAILURES ? disconnect : Effect.void,
+          ),
+        ),
+        Effect.ensuring(Effect.sleep("15 millis")),
+      ),
+    );
+
+  const applyLighting = Effect.gen(function* () {
+    if (!hid || !lighting) return;
+    const frame = lightingFrame(lighting, lightsOff);
+    if (isDeepEqual(frame, appliedFrame)) return;
+    appliedFrame = frame;
+    yield* call(RPC_METHODS.lightingConfig, frame.zones);
+    yield* call(RPC_METHODS.slotLighting, frame.slots);
+  }).pipe(
+    Effect.catch((error) =>
+      Effect.sync(() => {
+        appliedFrame = null;
+      }).pipe(Effect.andThen(Effect.logWarning("Codex Micro lighting update failed", error))),
+    ),
+  );
+
+  /** Input or a lighting change restores the lights and restarts auto-dim. */
+  const wake = () => {
+    if (dimFiber) runFork(Fiber.interrupt(dimFiber));
+    dimFiber = null;
+    const autoDimMs = lighting?.autoDimMs;
+    if (autoDimMs) {
+      dimFiber = runFork(
+        Effect.sleep(autoDimMs).pipe(
+          Effect.andThen(Effect.sync(() => (lightsOff = true))),
+          Effect.andThen(applyLighting),
+        ),
+      );
+    }
+    if (lightsOff) {
+      lightsOff = false;
+      runFork(applyLighting);
+    }
+  };
+
+  const onData = (data: Buffer) => {
+    const report = decodeReport(data);
+    if (!report || report.channel !== CHANNEL_RPC) return;
+    for (const line of lines.push(report)) {
+      const message = parseRpcLine(line);
+      if (!message) continue;
+      if (message.kind === "response") {
+        const response = pending.get(message.id);
+        if (!response) continue;
+        runFork(
+          message.error
+            ? Deferred.fail(response, new CodexMicroError({ message: message.error }))
+            : Deferred.succeed(response, message.result),
+        );
+        continue;
+      }
+      const { method, params } = message;
+      if (method === NOTIFY_METHODS.key && typeof params.k === "string") {
+        wake();
+        const act = typeof params.act === "number" ? params.act : null;
+        onEvent({ type: "input", input: { type: "key", key: params.k, act } });
+      } else if (
+        method === NOTIFY_METHODS.joystick &&
+        Number.isFinite(params.a) &&
+        Number.isFinite(params.d)
+      ) {
+        const angle = params.a as number;
+        const distance = params.d as number;
+        if (distance > 0.1) wake();
+        onEvent({ type: "input", input: { type: "joystick", angle, distance } });
+      }
+    }
+  };
+
+  const disconnect: Effect.Effect<void> = Effect.gen(function* () {
+    const device = hid;
+    consecutiveFailures = 0;
+    hid = null;
+    endpoint = null;
+    lines.reset();
+    for (const response of pending.values()) {
+      yield* Deferred.fail(response, new CodexMicroError({ message: "Codex Micro disconnected" }));
+    }
+    pending.clear();
+    if (device) {
+      // An unplugged or out-of-range device can reject close(); it must not
+      // stop the disconnected state from reaching the renderer.
+      yield* Effect.tryPromise({
+        try: () => device.close(),
+        catch: (cause) => new CodexMicroError({ message: "Could not close Codex Micro", cause }),
+      }).pipe(Effect.ignore);
+    }
+    setState(DISCONNECTED_CODEX_MICRO_STATE);
+  });
+
+  const refreshStatus = Effect.gen(function* () {
+    const status = (yield* call(RPC_METHODS.status, null)) as Record<string, unknown> | null;
+    setState({
+      connected: true,
+      transport: endpoint?.transport ?? null,
+      firmwareVersion: typeof status?.version === "string" ? status.version : null,
+      batteryPercentage: typeof status?.battery === "number" ? status.battery : null,
+      charging: typeof status?.is_charging === "boolean" ? status.is_charging : null,
+    });
+  }).pipe(Effect.catch((error) => Effect.logWarning("Codex Micro status request failed", error)));
+
+  const connect = Effect.fn("desktop.codexMicro.connect")(function* (next: CodexMicroEndpoint) {
+    const { HIDAsync } = yield* loadNodeHid;
+    // Non-exclusive so the keyboard and consumer collections keep working:
+    // over Bluetooth they share this very handle (macOS).
+    const device = yield* Effect.tryPromise({
+      try: () => HIDAsync.open(next.path, { nonExclusive: true }),
+      catch: (cause) => new CodexMicroError({ message: "Could not open Codex Micro", cause }),
+    });
+    device.on("data", onData);
+    device.on("error", (cause: unknown) => {
+      if (hid !== device) return;
+      runFork(
+        Effect.logWarning("Codex Micro connection lost", cause).pipe(Effect.andThen(disconnect)),
+      );
+    });
+    hid = device;
+    endpoint = next;
+    appliedFrame = null;
+    setState({ ...DISCONNECTED_CODEX_MICRO_STATE, connected: true, transport: next.transport });
+    wake();
+    yield* applyLighting;
+    yield* refreshStatus;
+  });
+
+  // Reconcile on every poll, even while connected: a Bluetooth handle can die
+  // silently (sleep, range, channel switch) and the cable moves the device to
+  // its USB endpoint. A failing open (usually Input Monitoring) backs off and
+  // logs once per distinct error.
+  let lastConnectError: string | null = null;
+  let retryAt = 0;
+  let lastTopology = "";
+  yield* Effect.gen(function* () {
+    const available = yield* listEndpoints;
+    const topology = available.map((candidate) => candidate.path).join("\n");
+    if (topology !== lastTopology) {
+      lastTopology = topology;
+      retryAt = 0;
+    }
+    const plan = reconcileEndpoint(hid ? endpoint : null, available);
+    if (plan.keep) return;
+    if (hid) yield* disconnect;
+    const now = yield* Clock.currentTimeMillis;
+    if (!plan.next || now < retryAt) return;
+    yield* connect(plan.next).pipe(
+      Effect.tapError(() => Effect.sync(() => (retryAt = now + RETRY_AFTER_FAILURE_MS))),
+    );
+    lastConnectError = null;
+  }).pipe(
+    Effect.catch((error) => {
+      const message = String(error.cause ?? error.message);
+      if (message === lastConnectError) return Effect.void;
+      lastConnectError = message;
+      return Effect.logWarning("Codex Micro connection failed", error);
+    }),
+    Effect.repeat(Schedule.spaced("2 seconds")),
+    Effect.forkScoped,
+  );
+  yield* Effect.gen(function* () {
+    if (hid) yield* refreshStatus;
+  }).pipe(Effect.delay("1 minute"), Effect.forever, Effect.forkScoped);
+
+  yield* Effect.addFinalizer(() =>
+    Effect.gen(function* () {
+      if (dimFiber) yield* Fiber.interrupt(dimFiber);
+      // Leave the device dark rather than frozen on stale thread states, but
+      // never hold up quitting on an unresponsive device.
+      lightsOff = true;
+      yield* applyLighting.pipe(
+        Effect.timeoutOrElse({ duration: "1 second", orElse: () => Effect.void }),
+      );
+      yield* disconnect;
+    }),
+  );
+
+  return {
+    getState: Effect.sync(() => state),
+    setLighting: Effect.fn("desktop.codexMicro.setLighting")(function* (next: CodexMicroLighting) {
+      const changed = !isDeepEqual(next, lighting);
+      lighting = next;
+      if (changed) wake();
+      yield* applyLighting;
+    }),
+  } satisfies CodexMicroDevice;
+});
```

## apps/desktop/src/codexMicro/CodexMicroLighting.ts

Status: added; +88; −0

```diff
@@ -1,0 +1,88 @@
+import {
+  CODEX_MICRO_STATUS_COLORS,
+  type CodexMicroLighting,
+  type CodexMicroSlotStatus,
+} from "@t3tools/contracts";
+
+// Firmware effect codes.
+const OFF = 0;
+const SOLID = 1;
+const SNAKE = 2;
+const BREATH = 4;
+const SHALLOW_BREATH = 6;
+
+const IDLE_DIM = 0.15;
+
+interface Zone {
+  readonly e: number;
+  readonly b: number;
+  readonly s: number;
+  readonly m: number;
+  readonly c: number;
+}
+
+const ZONE_OFF: Zone = { e: OFF, b: 0, s: 0, m: 0, c: 0 };
+
+export interface LightingFrame {
+  /** `v.oai.rgbcfg` params. */
+  readonly zones: { readonly keys: Zone; readonly ambient: Zone };
+  /** `v.oai.thstatus` params. */
+  readonly slots: ReadonlyArray<{
+    id: number;
+    c: number;
+    b: number;
+    e: number;
+    s: number;
+    sk: 0;
+    sa: 0;
+  }>;
+}
+
+const SLOT_EFFECT: Record<Exclude<CodexMicroSlotStatus, "off">, { e: number; s: number }> = {
+  working: { e: SHALLOW_BREATH, s: 0.4 },
+  "needs-input": { e: BREATH, s: 0.7 },
+  error: { e: SOLID, s: 0 },
+  done: { e: SOLID, s: 0 },
+  idle: { e: SOLID, s: 0 },
+};
+
+/** Maps what the renderer wants shown to raw firmware lighting params. */
+export function lightingFrame(lighting: CodexMicroLighting, lightsOff: boolean): LightingFrame {
+  const brightness = lightsOff ? 0 : lighting.brightness;
+  const slots = Array.from({ length: 6 }, (_, id) => {
+    const slot = lighting.slots[id];
+    if (lightsOff || !slot || slot.status === "off")
+      return { id, c: 0, b: 0, e: OFF, s: 0, sk: 0, sa: 0 } as const;
+    const effect = SLOT_EFFECT[slot.status];
+    // The selected key breathes so it's clear which thread the command keys target.
+    const highlight = slot.selected && effect.e === SOLID;
+    return {
+      id,
+      c: CODEX_MICRO_STATUS_COLORS[slot.status],
+      b: slot.selected || slot.status !== "idle" ? brightness : brightness * IDLE_DIM,
+      e: highlight ? SHALLOW_BREATH : effect.e,
+      s: highlight ? 0.3 : effect.s,
+      sk: 0,
+      sa: 0,
+    } as const;
+  });
+  if (lightsOff) return { zones: { keys: ZONE_OFF, ambient: ZONE_OFF }, slots };
+
+  // The ambient ring summarizes the most urgent state across all agent keys.
+  const has = (status: CodexMicroSlotStatus) =>
+    lighting.slots.some((slot) => slot.status === status);
+  const color = CODEX_MICRO_STATUS_COLORS;
+  const ambient: Zone = has("needs-input")
+    ? { e: BREATH, b: brightness, s: 0.6, m: 0, c: color["needs-input"] }
+    : has("working")
+      ? { e: SNAKE, b: brightness, s: 0.4, m: 0, c: color.working }
+      : has("error")
+        ? { e: SOLID, b: brightness * 0.5, s: 0, m: 0, c: color.error }
+        : has("done")
+          ? { e: SOLID, b: brightness * 0.4, s: 0, m: 0, c: color.done }
+          : ZONE_OFF;
+  const keys: Zone = lighting.keysBacklight
+    ? { e: SOLID, b: brightness * 0.3, s: 0, m: 0, c: color.idle }
+    : ZONE_OFF;
+  return { zones: { keys, ambient }, slots };
+}
```

## apps/desktop/src/codexMicro/CodexMicroProtocol.test.ts

Status: added; +121; −0

```diff
@@ -1,0 +1,121 @@
+import { assert, describe, it } from "@effect/vitest";
+
+import {
+  decodeReport,
+  encodeRpcRequest,
+  findEndpoints,
+  LineAssembler,
+  parseRpcLine,
+  reconcileEndpoint,
+} from "./CodexMicroProtocol.ts";
+import { lightingFrame } from "./CodexMicroLighting.ts";
+
+describe("Codex Micro protocol", () => {
+  it("splits requests into 64-byte reports and reassembles them", () => {
+    const params = { text: "x".repeat(150), title: "привет" };
+    const reports = encodeRpcRequest("v.oai.thstatus", params, 7);
+    assert.isAbove(reports.length, 2);
+    for (const report of reports) {
+      assert.strictEqual(report.length, 64);
+      assert.strictEqual(report[0], 6);
+      assert.strictEqual(report[1], 2);
+      assert.isAtMost(report[2] ?? 0, 61);
+    }
+    const lines = new LineAssembler();
+    const out = reports.flatMap((report) => lines.push(decodeReport(report)!));
+    out.push(...lines.push({ channel: 2, text: "\n" }));
+    assert.deepStrictEqual(JSON.parse(out[0]!), { method: "v.oai.thstatus", params, id: 7 });
+  });
+
+  it("decodes reports with or without the report id", () => {
+    const payload = [...new TextEncoder().encode("abc")];
+    assert.deepStrictEqual(decodeReport(Uint8Array.from([6, 2, 3, ...payload])), {
+      channel: 2,
+      text: "abc",
+    });
+    const withoutId = new Uint8Array(63);
+    withoutId.set([2, 3, ...payload]);
+    assert.deepStrictEqual(decodeReport(withoutId), { channel: 2, text: "abc" });
+  });
+
+  it("ignores keyboard, consumer and mouse reports sharing a Bluetooth handle", () => {
+    const consumer = new Uint8Array(64);
+    consumer.set([2, 3, 0xe9, 0x00]);
+    assert.isNull(decodeReport(consumer));
+    assert.isNull(decodeReport(Uint8Array.from([1, 0, 0, 4, 0, 0, 0, 0, 0])));
+    assert.isNull(decodeReport(Uint8Array.from([3, 0, 1, 255])));
+  });
+
+  const hid = (path: string, release: number, usagePage = 0xff00) => ({
+    path,
+    vendorId: 0x303a,
+    productId: 0x8360,
+    usagePage,
+    release,
+  });
+
+  it("finds vendor endpoints and prefers USB over Bluetooth", () => {
+    assert.deepStrictEqual(
+      findEndpoints([
+        hid("ble", 177),
+        hid("kbd", 176, 1),
+        hid("usb", 176),
+        { ...hid("x", 0), productId: 1 },
+      ]),
+      [
+        { path: "usb", transport: "usb" },
+        { path: "ble", transport: "bluetooth" },
+      ],
+    );
+  });
+
+  it("follows the live endpoint and hands off to USB", () => {
+    const usb = { path: "usb", transport: "usb" } as const;
+    const ble = { path: "ble", transport: "bluetooth" } as const;
+    assert.deepStrictEqual(reconcileEndpoint(null, []), { keep: false, next: null });
+    assert.deepStrictEqual(reconcileEndpoint(null, [ble]), { keep: false, next: ble });
+    assert.deepStrictEqual(reconcileEndpoint(ble, [ble]), { keep: true });
+    assert.deepStrictEqual(reconcileEndpoint(ble, [usb, ble]), { keep: false, next: usb });
+    assert.deepStrictEqual(reconcileEndpoint(usb, [usb, ble]), { keep: true });
+    // Unplugging the cable (or a new pairing handle after sleep) moves to what is left.
+    assert.deepStrictEqual(reconcileEndpoint(usb, [ble]), { keep: false, next: ble });
+    assert.deepStrictEqual(reconcileEndpoint(ble, []), { keep: false, next: null });
+  });
+
+  it("parses responses and abbreviated notifications", () => {
+    assert.deepStrictEqual(parseRpcLine(`{"result":{"ok":1},"id":42}`), {
+      kind: "response",
+      id: "42",
+      result: { ok: 1 },
+      error: null,
+    });
+    assert.deepStrictEqual(parseRpcLine(`{"m":"v.oai.hid","p":{"k":"AG01","act":1}}`), {
+      kind: "notification",
+      method: "v.oai.hid",
+      params: { k: "AG01", act: 1 },
+    });
+    assert.isNull(parseRpcLine("garbage"));
+  });
+});
+
+describe("Codex Micro lighting", () => {
+  const lighting = {
+    slots: [
+      { status: "needs-input", selected: false },
+      { status: "working", selected: true },
+      { status: "idle", selected: false },
+    ],
+    brightness: 0.8,
+    autoDimMs: null,
+    keysBacklight: false,
+  } as const;
+
+  it("lights the ring with the most urgent state and keys per thread", () => {
+    const frame = lightingFrame(lighting, false);
+    assert.strictEqual(frame.zones.ambient.c, 0xff6d00);
+    assert.strictEqual(frame.slots.length, 6);
+    assert.strictEqual(frame.slots[1]?.c, 0x304ffe);
+    assert.isBelow(frame.slots[2]?.b ?? 1, 0.8);
+    assert.strictEqual(frame.slots[5]?.b, 0);
+  });
+});
```

## apps/desktop/src/codexMicro/CodexMicroProtocol.ts

Status: added; +189; −0

```diff
@@ -1,0 +1,189 @@
+// Codex Micro vendor HID channel (usage page 0xFF00): 64-byte reports laid out as
+// [reportId=6, channel, payloadLength, ...payload(<=61)]. Channel 2 carries
+// newline-delimited JSON-RPC, channel 1 firmware logs. Over Bluetooth LE the
+// keyboard, consumer and mouse collections share the same HID handle.
+
+import type { CodexMicroTransport } from "@t3tools/contracts";
+
+const WORK_LOUDER_VENDOR_ID = 0x303a;
+const CODEX_MICRO_PRODUCT_ID = 0x8360;
+const VENDOR_USAGE_PAGE = 0xff00;
+
+const REPORT_ID = 6;
+const REPORT_SIZE = 64;
+const MAX_PAYLOAD = REPORT_SIZE - 3;
+export const CHANNEL_RPC = 2;
+
+/** Firmware only accepts ids in [0, 999). */
+export const MAX_RPC_ID = 999;
+
+export const RPC_METHODS = {
+  status: "device.status",
+  lightingConfig: "v.oai.rgbcfg",
+  slotLighting: "v.oai.thstatus",
+} as const;
+
+export const NOTIFY_METHODS = {
+  key: "v.oai.hid",
+  joystick: "v.oai.rad",
+} as const;
+
+export function encodeRpcRequest(method: string, params: unknown, id: number): Uint8Array[] {
+  // The firmware JSON parser is ASCII-only.
+  const json = JSON.stringify({ method, params, id }).replace(
+    /[^\x00-\x7f]/g,
+    (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`,
+  );
+  const bytes = new TextEncoder().encode(json);
+  const reports: Uint8Array[] = [];
+  for (let offset = 0; offset < bytes.length; offset += MAX_PAYLOAD) {
+    const chunk = bytes.subarray(offset, offset + MAX_PAYLOAD);
+    const report = new Uint8Array(REPORT_SIZE);
+    report[0] = REPORT_ID;
+    report[1] = CHANNEL_RPC;
+    report[2] = chunk.length;
+    report.set(chunk, 3);
+    reports.push(report);
+  }
+  return reports;
+}
+
+export interface DecodedReport {
+  readonly channel: number;
+  readonly text: string;
+}
+
+/**
+ * Input reports arrive with or without the report id depending on the OS
+ * backend. A full-size report that starts with another id is keyboard, mouse
+ * or consumer input sharing the handle (Bluetooth), not vendor traffic.
+ */
+export function decodeReport(data: Uint8Array): DecodedReport | null {
+  const base = data[0] === REPORT_ID ? 1 : data.length === REPORT_SIZE - 1 ? 0 : -1;
+  if (base < 0) return null;
+  const channel = data[base];
+  const length = data[base + 1];
+  if (channel === undefined || length === undefined || length > MAX_PAYLOAD) return null;
+  return {
+    channel,
+    text: new TextDecoder().decode(data.subarray(base + 2, base + 2 + length)),
+  };
+}
+
+export interface HidCandidate {
+  readonly path?: string | undefined;
+  readonly vendorId: number;
+  readonly productId: number;
+  readonly usagePage?: number | undefined;
+  readonly release: number;
+}
+
+export interface CodexMicroEndpoint {
+  readonly path: string;
+  readonly transport: CodexMicroTransport;
+}
+
+/** The firmware sets the low two bits of bcdDevice for wireless links. */
+const transportOf = (device: HidCandidate): CodexMicroTransport =>
+  device.release % 4 === 0 ? "usb" : "bluetooth";
+
+/** Every reachable vendor endpoint, USB first: a cable is faster and never sleeps. */
+export function findEndpoints(devices: ReadonlyArray<HidCandidate>): CodexMicroEndpoint[] {
+  return devices
+    .flatMap((device) =>
+      device.path &&
+      device.vendorId === WORK_LOUDER_VENDOR_ID &&
+      device.productId === CODEX_MICRO_PRODUCT_ID &&
+      device.usagePage === VENDOR_USAGE_PAGE
+        ? [{ path: device.path, transport: transportOf(device) }]
+        : [],
+    )
+    .toSorted(
+      (left, right) => Number(right.transport === "usb") - Number(left.transport === "usb"),
+    );
+}
+
+/**
+ * Keeps the open endpoint while it is still listed and nothing better is,
+ * otherwise names the endpoint to move to (or `null` when the device is gone).
+ */
+export function reconcileEndpoint(
+  current: CodexMicroEndpoint | null,
+  available: ReadonlyArray<CodexMicroEndpoint>,
+): { readonly keep: true } | { readonly keep: false; readonly next: CodexMicroEndpoint | null } {
+  const best = available[0] ?? null;
+  if (
+    current &&
+    available.some((endpoint) => endpoint.path === current.path) &&
+    (current.transport === "usb" || best?.transport !== "usb")
+  ) {
+    return { keep: true };
+  }
+  return { keep: false, next: best };
+}
+
+/** Reassembles newline-delimited lines per channel. */
+export class LineAssembler {
+  private readonly buffers = new Map<number, string>();
+
+  push(report: DecodedReport): string[] {
+    const parts = ((this.buffers.get(report.channel) ?? "") + report.text).split(/\r?\n/);
+    this.buffers.set(report.channel, parts.pop() ?? "");
+    return parts.map((line) => line.trim()).filter((line) => line.length > 0);
+  }
+
+  reset(): void {
+    this.buffers.clear();
+  }
+}
+
+export type RpcLine =
+  | {
+      readonly kind: "response";
+      readonly id: string;
+      readonly result: unknown;
+      readonly error: string | null;
+    }
+  | {
+      readonly kind: "notification";
+      readonly method: string;
+      readonly params: Record<string, unknown>;
+    };
+
+/** The firmware sometimes abbreviates `id/method/params` to `i/m/p`. */
+export function parseRpcLine(line: string): RpcLine | null {
+  let message: Record<string, unknown>;
+  try {
+    message = JSON.parse(line.slice(line.indexOf("{"))) as Record<string, unknown>;
+  } catch {
+    return null;
+  }
+  const id = message.id ?? message.i;
+  if (id !== undefined && id !== null) {
+    const error = message.error as { message?: unknown } | undefined;
+    return {
+      kind: "response",
+      id: String(id),
+      result: message.result,
+      error: error ? String(error.message ?? "rpc error") : null,
+    };
+  }
+  const method = message.method ?? message.m;
+  if (typeof method !== "string") return null;
+  const params = (message.params ?? message.p ?? {}) as Record<string, unknown>;
+  return { kind: "notification", method, params };
+}
+
+/** Structural equality for the plain JSON-like values we diff before writing. */
+export function isDeepEqual(left: unknown, right: unknown): boolean {
+  if (left === right) return true;
+  if (typeof left !== "object" || typeof right !== "object" || left === null || right === null) {
+    return false;
+  }
+  if (Array.isArray(left) !== Array.isArray(right)) return false;
+  const leftKeys = Object.keys(left);
+  if (leftKeys.length !== Object.keys(right).length) return false;
+  return leftKeys.every((key) =>
+    isDeepEqual((left as Record<string, unknown>)[key], (right as Record<string, unknown>)[key]),
+  );
+}
```

## apps/desktop/src/ipc/DesktopIpcHandlers.ts

Status: modified; +2; −0

```diff
@@ -3,6 +3,7 @@
 import { receiveProviderAuthCallback, cancelProviderAuthCallback } from "./methods/providerAuth.ts";
 import * as DesktopIpc from "./DesktopIpc.ts";
 import { installNotificationBadge } from "./methods/notificationBadge.ts";
+import { installCodexMicro } from "./methods/codexMicro.ts";
 import { getClientSettings, setClientSettings } from "./methods/clientSettings.ts";
 import {
   clearConnectionCatalog,
@@ -87,6 +88,7 @@
 export const installDesktopIpcHandlers = Effect.fn("desktop.ipc.installHandlers")(function* () {
   const ipc = yield* DesktopIpc.DesktopIpc;
   yield* installNotificationBadge();
+  yield* installCodexMicro();
   yield* PreviewIpc.installPreviewEventForwarding();
 
   yield* ipc.handle(AppActivationIpc.setReady);
```

## apps/desktop/src/ipc/channels.ts

Status: modified; +3; −0

```diff
@@ -125,3 +125,6 @@
 export const CANCEL_PROVIDER_AUTH_CALLBACK_CHANNEL = "desktop:cancel-provider-auth-callback";
 export const TAKE_LEGACY_LOCAL_STORAGE_CHANNEL = "desktop:take-legacy-local-storage";
 export const COMPLETE_LEGACY_LOCAL_STORAGE_CHANNEL = "desktop:complete-legacy-local-storage";
+export const CODEX_MICRO_GET_STATE_CHANNEL = "desktop:codex-micro-get-state";
+export const CODEX_MICRO_SET_LIGHTING_CHANNEL = "desktop:codex-micro-set-lighting";
+export const CODEX_MICRO_EVENT_CHANNEL = "desktop:codex-micro-event";
```

## apps/desktop/src/ipc/methods/codexMicro.ts

Status: added; +45; −0

```diff
@@ -1,0 +1,45 @@
+import * as Electron from "electron";
+import * as Effect from "effect/Effect";
+import * as Schema from "effect/Schema";
+import {
+  CodexMicroDeviceState,
+  CodexMicroLighting,
+  type CodexMicroEvent,
+} from "@t3tools/contracts";
+
+import { makeCodexMicroDevice } from "../../codexMicro/CodexMicroDevice.ts";
+import * as DesktopIpc from "../DesktopIpc.ts";
+import {
+  CODEX_MICRO_EVENT_CHANNEL,
+  CODEX_MICRO_GET_STATE_CHANNEL,
+  CODEX_MICRO_SET_LIGHTING_CHANNEL,
+} from "../channels.ts";
+
+function broadcast(event: CodexMicroEvent): void {
+  for (const window of Electron.BrowserWindow.getAllWindows()) {
+    if (!window.isDestroyed()) window.webContents.send(CODEX_MICRO_EVENT_CHANNEL, event);
+  }
+}
+
+/** Owns the Codex Micro HID connection for the app's lifetime. */
+export const installCodexMicro = Effect.fn("desktop.ipc.installCodexMicro")(function* () {
+  const ipc = yield* DesktopIpc.DesktopIpc;
+  const device = yield* makeCodexMicroDevice(broadcast);
+
+  yield* ipc.handle(
+    DesktopIpc.makeIpcMethod({
+      channel: CODEX_MICRO_GET_STATE_CHANNEL,
+      payload: Schema.Void,
+      result: CodexMicroDeviceState,
+      handler: () => device.getState,
+    }),
+  );
+  yield* ipc.handle(
+    DesktopIpc.makeIpcMethod({
+      channel: CODEX_MICRO_SET_LIGHTING_CHANNEL,
+      payload: CodexMicroLighting,
+      result: Schema.Void,
+      handler: device.setLighting,
+    }),
+  );
+});
```

## apps/desktop/src/preload.ts

Status: modified; +15; −0

```diff
@@ -218,6 +218,21 @@
       ipcRenderer.removeListener(IpcChannels.MENU_ACTION_CHANNEL, wrappedListener);
     };
   },
+  codexMicro: {
+    getState: () => ipcRenderer.invoke(IpcChannels.CODEX_MICRO_GET_STATE_CHANNEL),
+    setLighting: (lighting) =>
+      ipcRenderer.invoke(IpcChannels.CODEX_MICRO_SET_LIGHTING_CHANNEL, lighting),
+    onEvent: (listener) => {
+      const wrappedListener = (_event: Electron.IpcRendererEvent, event: unknown) => {
+        if (typeof event === "object" && event !== null && "type" in event) {
+          listener(event as Parameters<typeof listener>[0]);
+        }
+      };
+      ipcRenderer.on(IpcChannels.CODEX_MICRO_EVENT_CHANNEL, wrappedListener);
+      return () =>
+        ipcRenderer.removeListener(IpcChannels.CODEX_MICRO_EVENT_CHANNEL, wrappedListener);
+    },
+  },
   onSnapShotEvent: (listener) => {
     const wrappedListener = (_event: Electron.IpcRendererEvent, event: unknown) => {
       if (!isSnapShotEvent(event)) return;
```

## apps/web/src/codexMicro/codexMicroActions.test.ts

Status: added; +31; −0

```diff
@@ -1,0 +1,31 @@
+import { describe, expect, it } from "vite-plus/test";
+
+import { codexMicroShortcutEvent } from "./codexMicroActions";
+
+describe("codexMicroShortcutEvent", () => {
+  it("replays the user's binding with the platform modifier", () => {
+    const keybindings = [
+      {
+        command: "terminal.toggle" as const,
+        shortcut: {
+          key: "j",
+          metaKey: false,
+          ctrlKey: false,
+          shiftKey: false,
+          altKey: false,
+          modKey: true,
+        },
+      },
+    ];
+    expect(codexMicroShortcutEvent(keybindings, "terminal.toggle", "MacIntel")).toMatchObject({
+      key: "j",
+      metaKey: true,
+      ctrlKey: false,
+    });
+    expect(codexMicroShortcutEvent(keybindings, "terminal.toggle", "Win32")).toMatchObject({
+      metaKey: false,
+      ctrlKey: true,
+    });
+    expect(codexMicroShortcutEvent(keybindings, "diff.toggle", "MacIntel")).toBeNull();
+  });
+});
```

## apps/web/src/codexMicro/codexMicroActions.ts

Status: added; +162; −0

```diff
@@ -1,0 +1,162 @@
+import {
+  STATIC_KEYBINDING_COMMANDS,
+  type CodexMicroAction,
+  type CodexMicroControlId,
+  type CodexMicroKnobMode,
+  type KeybindingCommand,
+  type ResolvedKeybindingsConfig,
+} from "@t3tools/contracts";
+import { isMacPlatform } from "@t3tools/shared/keybindings";
+
+import { effectiveShortcutsForCommand } from "../keybindings";
+import { commandLabel } from "../components/settings/KeybindingsSettings.logic";
+
+export interface CodexMicroActionOption {
+  readonly key: string;
+  readonly action: CodexMicroAction;
+  readonly label: string;
+}
+
+export interface CodexMicroActionGroup {
+  readonly title: string;
+  readonly options: ReadonlyArray<CodexMicroActionOption>;
+}
+
+// Plain names for the commands people put on a desk controller; the rest use
+// the Keybindings settings label.
+const COMMAND_LABELS: Partial<Record<KeybindingCommand, string>> = {
+  "chat.new": "New thread",
+  "chat.newLocal": "New local thread",
+  "chat.newWithoutProject": "New thread without project",
+  "chat.find": "Find in thread",
+  "terminal.toggle": "Toggle terminal",
+  "diff.toggle": "Toggle diff",
+  "sidebar.toggle": "Toggle sidebar",
+  "rightPanel.toggle": "Toggle right panel",
+  "commandPalette.toggle": "Command palette",
+  "filePicker.toggle": "Go to file",
+  "projectSearch.toggle": "Search project",
+  "thread.previous": "Previous thread",
+  "thread.next": "Next thread",
+  "thread.pin": "Pin thread",
+  "thread.settle": "Settle thread",
+  "navigation.back": "Back",
+  "navigation.forward": "Forward",
+  "modelPicker.toggle": "Model picker",
+  "composer.effort": "Reasoning effort",
+  "composer.mode": "Interaction mode",
+};
+
+const DEVICE_ACTION_LABELS = {
+  none: "None",
+  approve: "Approve",
+  approveForSession: "Approve for session",
+  decline: "Decline",
+  stop: "Stop thread",
+  nextSlot: "Open next agent key",
+  previousSlot: "Open previous agent key",
+  focusApp: "Bring T3 Code forward",
+} as const satisfies Record<Exclude<CodexMicroAction["type"], "command">, string>;
+
+const option = (action: CodexMicroAction): CodexMicroActionOption => ({
+  key: codexMicroActionKey(action),
+  action,
+  label: describeCodexMicroAction(action),
+});
+
+// Commands that depend on a focused picker or page make no sense from a desk controller.
+const HIDDEN_COMMAND_PREFIXES = [
+  "modelPicker.jump.",
+  "usage.",
+  "thread.jump.",
+  "preview.",
+  "terminal.split",
+];
+
+export const CODEX_MICRO_ACTION_GROUPS: ReadonlyArray<CodexMicroActionGroup> = [
+  {
+    title: "Agent",
+    options: (["approve", "approveForSession", "decline", "stop"] as const).map((type) =>
+      option({ type }),
+    ),
+  },
+  {
+    title: "Agent keys",
+    options: (["nextSlot", "previousSlot", "focusApp"] as const).map((type) => option({ type })),
+  },
+  {
+    title: "Commands",
+    options: STATIC_KEYBINDING_COMMANDS.filter(
+      (command) => !HIDDEN_COMMAND_PREFIXES.some((prefix) => command.startsWith(prefix)),
+    )
+      .map((command) => option({ type: "command", command }))
+      .toSorted((a, b) => a.label.localeCompare(b.label)),
+  },
+  { title: "Other", options: [option({ type: "none" })] },
+];
+
+export function codexMicroActionKey(action: CodexMicroAction): string {
+  return action.type === "command" ? `command:${action.command}` : action.type;
+}
+
+export function describeCodexMicroAction(action: CodexMicroAction): string {
+  if (action.type !== "command") return DEVICE_ACTION_LABELS[action.type];
+  return COMMAND_LABELS[action.command] ?? commandLabel(action.command);
+}
+
+const KNOB_PRESETS: Record<
+  Exclude<CodexMicroKnobMode, "custom">,
+  Partial<Record<CodexMicroControlId, CodexMicroAction>>
+> = {
+  agents: {
+    ENC_CW: { type: "nextSlot" },
+    ENC_CC: { type: "previousSlot" },
+    ENC: { type: "focusApp" },
+  },
+  threads: {
+    ENC_CW: { type: "command", command: "thread.next" },
+    ENC_CC: { type: "command", command: "thread.previous" },
+    ENC: { type: "command", command: "commandPalette.toggle" },
+  },
+};
+
+/** The action a control runs, with the knob preset applied. */
+export function resolveCodexMicroControl(
+  control: CodexMicroControlId,
+  knobMode: CodexMicroKnobMode,
+  controls: Readonly<Record<CodexMicroControlId, CodexMicroAction>>,
+): CodexMicroAction {
+  return (knobMode === "custom" ? undefined : KNOB_PRESETS[knobMode][control]) ?? controls[control];
+}
+
+/** Codex's mapping: angle 0 = right, 0.25 = down, 0.5 = left, 0.75 = up. */
+export function codexMicroJoystickControl(angle: number): CodexMicroControlId {
+  if (angle >= 0.625 && angle < 0.875) return "JOY_UP";
+  if (angle >= 0.125 && angle < 0.375) return "JOY_DOWN";
+  if (angle >= 0.375 && angle < 0.625) return "JOY_LEFT";
+  return "JOY_RIGHT";
+}
+
+/**
+ * Commands are handled by per-feature keydown listeners, so the device replays
+ * the user's own shortcut for the command, like the HTML render frame does.
+ * That keeps every command, rebinding and `when` clause working unchanged.
+ */
+export function codexMicroShortcutEvent(
+  keybindings: ResolvedKeybindingsConfig,
+  command: KeybindingCommand,
+  platform: string,
+): KeyboardEventInit | null {
+  const shortcut = effectiveShortcutsForCommand(keybindings, command, { platform })[0];
+  if (!shortcut) return null;
+  const mac = isMacPlatform(platform);
+  return {
+    key: shortcut.key,
+    metaKey: shortcut.metaKey || (shortcut.modKey && mac),
+    ctrlKey: shortcut.ctrlKey || (shortcut.modKey && !mac),
+    shiftKey: shortcut.shiftKey,
+    altKey: shortcut.altKey,
+    bubbles: true,
+    cancelable: true,
+  };
+}
```

## apps/web/src/codexMicro/codexMicroSlots.test.ts

Status: added; +101; −0

```diff
@@ -1,0 +1,101 @@
+import { ProviderInstanceId, ThreadId } from "@t3tools/contracts";
+import { describe, expect, it } from "vite-plus/test";
+
+import { makeThreadFixture, type ThreadFixtureOverrides } from "../test-fixtures";
+import {
+  assignCodexMicroSlots,
+  codexMicroSlotStatus,
+  EMPTY_CODEX_MICRO_SLOTS,
+} from "./codexMicroSlots";
+
+const NOW = "2026-10-22T12:00:00.000Z";
+const running = {
+  status: "running" as const,
+  activeRunId: null,
+  providerInstanceId: ProviderInstanceId.make("codex"),
+  providerName: "Codex",
+  lastError: null,
+  updatedAt: NOW,
+};
+
+const thread = (id: string, overrides: ThreadFixtureOverrides = {}) =>
+  makeThreadFixture({
+    id: ThreadId.make(id),
+    title: id,
+    updatedAt: "2026-10-22T10:00:00.000Z",
+    ...overrides,
+  });
+
+const assign = (
+  threads: ReturnType<typeof thread>[],
+  options: Partial<Parameters<typeof assignCodexMicroSlots>[0]> = {},
+) =>
+  assignCodexMicroSlots({
+    threads,
+    previous: EMPTY_CODEX_MICRO_SLOTS,
+    mode: "priority",
+    pinnedThreads: [],
+    now: NOW,
+    ...options,
+  });
+
+describe("codexMicroSlotStatus", () => {
+  it("folds sidebar states into device colors", () => {
+    expect(codexMicroSlotStatus(thread("a", { hasPendingApprovals: true, runtime: running }))).toBe(
+      "needs-input",
+    );
+    expect(codexMicroSlotStatus(thread("a", { pendingGuestMessageCount: 1 }))).toBe("needs-input");
+    expect(codexMicroSlotStatus(thread("a", { runtime: running }))).toBe("working");
+    expect(codexMicroSlotStatus(thread("a"))).toBe("idle");
+  });
+});
+
+describe("assignCodexMicroSlots", () => {
+  it("puts urgent threads first, then keeps them on their keys", () => {
+    const first = assign([thread("idle"), thread("run", { runtime: running })]);
+    expect(first.map((slot) => slot.threadId).slice(0, 2)).toEqual(["run", "idle"]);
+
+    // "idle" now needs input and outranks "run", but it must not jump keys.
+    const next = assign(
+      [thread("idle", { hasPendingApprovals: true }), thread("run", { runtime: running })],
+      { previous: first },
+    );
+    expect(next.map((slot) => slot.threadId).slice(0, 2)).toEqual(["run", "idle"]);
+    expect(next[1]?.status).toBe("needs-input");
+  });
+
+  it("skips settled, archived and snoozed threads unless they're live", () => {
+    const slots = assign([
+      thread("settled", { settledAt: NOW }),
+      thread("archived", { archivedAt: NOW, runtime: running }),
+      thread("snoozed", { snoozedUntil: "2026-10-23T00:00:00.000Z" }),
+      thread("settled-but-running", { settledAt: NOW, runtime: running }),
+    ]);
+    expect(slots.map((slot) => slot.threadId).filter(Boolean)).toEqual(["settled-but-running"]);
+  });
+
+  it("honours pins and the pinned/recent modes", () => {
+    const threads = [
+      thread("p2", { pinnedAt: NOW, pinOrderKey: "b" }),
+      thread("p1", { pinnedAt: NOW, pinOrderKey: "a" }),
+      thread("new", { updatedAt: "2026-10-22T11:00:00.000Z", settledAt: NOW }),
+    ];
+    expect(
+      assign(threads, { mode: "pinned" })
+        .map((slot) => slot.threadId)
+        .slice(0, 3),
+    ).toEqual(["p1", "p2", null]);
+    expect(assign(threads, { mode: "recent" })[0]?.threadId).toBe("new");
+    const pinned = assign(threads, { pinnedThreads: [null, null, null, null, null, "new"] });
+    expect(pinned[5]?.threadId).toBe("new");
+  });
+
+  it("gives a thread pinned to two keys only the first", () => {
+    const threads = ["a", "b", "c", "d", "e", "f"].map((id) => thread(id));
+    const slots = assign(threads, { pinnedThreads: ["a", "a", null, null, null, null] });
+    expect(slots[0]?.threadId).toBe("a");
+    expect(new Set(slots.map((slot) => slot.threadId))).toEqual(
+      new Set(["a", "b", "c", "d", "e", "f"]),
+    );
+  });
+});
```

## apps/web/src/codexMicro/codexMicroSlots.ts

Status: added; +151; −0

```diff
@@ -1,0 +1,151 @@
+import {
+  CODEX_MICRO_SLOT_COUNT,
+  type CodexMicroAgentMode,
+  type CodexMicroSlotStatus,
+  type EnvironmentId,
+  type ThreadId,
+} from "@t3tools/contracts";
+
+import { hasUnseenCompletion, resolveSidebarThreadStatus } from "../components/Sidebar.logic";
+import type { SidebarThreadSummary } from "../types";
+
+export interface CodexMicroSlot {
+  readonly index: number;
+  readonly environmentId: EnvironmentId | null;
+  readonly threadId: ThreadId | null;
+  readonly title: string | null;
+  readonly status: CodexMicroSlotStatus;
+}
+
+export const EMPTY_CODEX_MICRO_SLOTS: ReadonlyArray<CodexMicroSlot> = Array.from(
+  { length: CODEX_MICRO_SLOT_COUNT },
+  (_, index) => ({ index, environmentId: null, threadId: null, title: null, status: "off" }),
+);
+
+/** The same states the sidebar shows, folded into the device's five colors. */
+export function codexMicroSlotStatus(thread: SidebarThreadSummary): CodexMicroSlotStatus {
+  switch (resolveSidebarThreadStatus(thread)) {
+    case "approval":
+    case "input":
+    case "request":
+      return "needs-input";
+    case "working":
+      return "working";
+    case "waiting":
+      return thread.blockedOnThreadWait === true ? "working" : "idle";
+    case "failed":
+    case "limited":
+      return "error";
+    case "ready":
+      return hasUnseenCompletion(thread) ? "done" : "idle";
+  }
+}
+
+const PRIORITY: Record<CodexMicroSlotStatus, number> = {
+  "needs-input": 0,
+  error: 1,
+  working: 2,
+  done: 3,
+  idle: 4,
+  off: 5,
+};
+
+function isVisible(thread: SidebarThreadSummary, now: string): boolean {
+  if (thread.deletedAt || thread.archivedAt) return false;
+  return !thread.snoozedUntil || thread.snoozedUntil <= now;
+}
+
+function rank(
+  threads: ReadonlyArray<SidebarThreadSummary>,
+  mode: CodexMicroAgentMode,
+  now: string,
+): SidebarThreadSummary[] {
+  const visible = threads.filter((thread) => isVisible(thread, now));
+  switch (mode) {
+    case "pinned":
+      // Sidebar "Pinned" order.
+      return visible
+        .filter((thread) => thread.pinnedAt)
+        .toSorted(
+          (a, b) =>
+            (a.pinOrderKey ?? "").localeCompare(b.pinOrderKey ?? "") ||
+            (a.pinnedAt ?? "").localeCompare(b.pinnedAt ?? ""),
+        );
+    case "recent":
+      return visible.toSorted((a, b) => b.updatedAt.localeCompare(a.updatedAt));
+    case "priority":
+      return (
+        visible
+          .map((thread) => ({ thread, status: codexMicroSlotStatus(thread) }))
+          // Live work always shows; finished or idle threads only while in the inbox.
+          .filter(
+            ({ thread, status }) =>
+              status === "working" ||
+              status === "needs-input" ||
+              thread.pinnedAt !== null ||
+              thread.settledAt === null,
+          )
+          .toSorted(
+            (a, b) =>
+              PRIORITY[a.status] - PRIORITY[b.status] ||
+              Number(b.thread.pinnedAt !== null) - Number(a.thread.pinnedAt !== null) ||
+              b.thread.updatedAt.localeCompare(a.thread.updatedAt),
+          )
+          .map(({ thread }) => thread)
+      );
+  }
+}
+
+/**
+ * Picks the threads for the six agent keys. A thread keeps its key while it
+ * stays in the top six, so lights don't jump around; freed keys go to the next
+ * thread in line. `pinnedThreads` hard-assigns keys.
+ */
+export function assignCodexMicroSlots(input: {
+  readonly threads: ReadonlyArray<SidebarThreadSummary>;
+  readonly previous: ReadonlyArray<CodexMicroSlot>;
+  readonly mode: CodexMicroAgentMode;
+  readonly pinnedThreads: ReadonlyArray<string | null>;
+  readonly now: string;
+}): CodexMicroSlot[] {
+  const byId = new Map(input.threads.map((thread) => [thread.id as string, thread]));
+  // A thread pinned to two keys keeps only the first.
+  const pinned = new Set<string>();
+  const pins = Array.from({ length: CODEX_MICRO_SLOT_COUNT }, (_, index) => {
+    const id = input.pinnedThreads[index] ?? null;
+    if (id === null || !byId.has(id) || pinned.has(id)) return null;
+    pinned.add(id);
+    return id;
+  });
+  const ranked = rank(input.threads, input.mode, input.now).filter(
+    (thread) => !pinned.has(thread.id),
+  );
+  const chosen = new Set(
+    ranked.slice(0, CODEX_MICRO_SLOT_COUNT - pinned.size).map((thread) => thread.id as string),
+  );
+
+  const ids: (string | null)[] = [...pins];
+  for (const slot of input.previous) {
+    if (ids[slot.index] === null && slot.threadId !== null && chosen.has(slot.threadId)) {
+      ids[slot.index] = slot.threadId;
+      chosen.delete(slot.threadId);
+    }
+  }
+  const rest = ranked.filter((thread) => chosen.has(thread.id));
+  for (let index = 0; index < CODEX_MICRO_SLOT_COUNT && rest.length > 0; index += 1) {
+    if (ids[index] === null) ids[index] = rest.shift()!.id;
+  }
+
+  return ids.map((id, index) => {
+    const thread = id === null ? undefined : byId.get(id);
+    return thread
+      ? {
+          index,
+          environmentId: thread.environmentId,
+          threadId: thread.id,
+          title: thread.title,
+          status: codexMicroSlotStatus(thread),
+        }
+      : EMPTY_CODEX_MICRO_SLOTS[index]!;
+  });
+}
```

## apps/web/src/codexMicro/codexMicroStore.ts

Status: added; +118; −0

```diff
@@ -1,0 +1,118 @@
+import {
+  DISCONNECTED_CODEX_MICRO_STATE,
+  type CodexMicroDeviceState,
+  type CodexMicroInputEvent,
+} from "@t3tools/contracts";
+import { useSyncExternalStore } from "react";
+
+import { EMPTY_CODEX_MICRO_SLOTS, type CodexMicroSlot } from "./codexMicroSlots";
+
+/**
+ * Renderer-side Codex Micro state shared by the headless coordinator (which
+ * owns slots and selection) and the settings page (which previews them).
+ */
+export interface CodexMicroSnapshot {
+  /** False until the main process has reported the device state once. */
+  readonly loaded: boolean;
+  readonly device: CodexMicroDeviceState;
+  readonly slots: ReadonlyArray<CodexMicroSlot>;
+  readonly selectedSlot: number | null;
+  readonly pressedKeys: ReadonlySet<string>;
+  readonly joystick: { readonly angle: number; readonly distance: number };
+}
+
+let snapshot: CodexMicroSnapshot = {
+  loaded: false,
+  device: DISCONNECTED_CODEX_MICRO_STATE,
+  slots: EMPTY_CODEX_MICRO_SLOTS,
+  selectedSlot: null,
+  pressedKeys: new Set(),
+  joystick: { angle: 0, distance: 0 },
+};
+const listeners = new Set<() => void>();
+const inputListeners = new Set<(input: CodexMicroInputEvent) => void>();
+let unsubscribeBridge: (() => void) | null = null;
+
+const bridge = () => (typeof window === "undefined" ? undefined : window.desktopBridge?.codexMicro);
+
+function update(patch: Partial<CodexMicroSnapshot>) {
+  snapshot = { ...snapshot, ...patch };
+  for (const listener of listeners) listener();
+}
+
+function handleInput(input: CodexMicroInputEvent) {
+  if (input.type === "joystick") {
+    update({ joystick: { angle: input.angle, distance: input.distance } });
+  } else if (input.act === 0 || input.act === 1) {
+    const pressedKeys = new Set(snapshot.pressedKeys);
+    if (input.act === 1) pressedKeys.add(input.key);
+    else pressedKeys.delete(input.key);
+    update({ pressedKeys });
+  }
+  for (const listener of inputListeners) listener(input);
+}
+
+function connectBridge() {
+  const codexMicro = bridge();
+  if (!codexMicro || unsubscribeBridge) return;
+  // A state event can land before the initial getState() reply; the event is newer.
+  let receivedStateEvent = false;
+  unsubscribeBridge = codexMicro.onEvent((event) => {
+    if (event.type === "state") {
+      receivedStateEvent = true;
+      update({
+        loaded: true,
+        device: event.state,
+        ...(event.state.connected ? {} : { pressedKeys: new Set<string>(), selectedSlot: null }),
+      });
+    } else {
+      handleInput(event.input);
+    }
+  });
+  void codexMicro.getState().then((device) => {
+    if (!receivedStateEvent) update({ loaded: true, device });
+  });
+}
+
+function subscribe(listener: () => void) {
+  connectBridge();
+  listeners.add(listener);
+  return () => listeners.delete(listener);
+}
+
+export function useCodexMicroSnapshot(): CodexMicroSnapshot {
+  return useSyncExternalStore(
+    subscribe,
+    () => snapshot,
+    () => snapshot,
+  );
+}
+
+/** True while a Codex Micro is attached to this desktop app. */
+export function useCodexMicroConnected(): boolean {
+  return useSyncExternalStore(
+    subscribe,
+    () => snapshot.device.connected,
+    () => false,
+  );
+}
+
+export function subscribeCodexMicroInput(listener: (input: CodexMicroInputEvent) => void) {
+  connectBridge();
+  inputListeners.add(listener);
+  return () => inputListeners.delete(listener);
+}
+
+export function readCodexMicroSnapshot(): CodexMicroSnapshot {
+  return snapshot;
+}
+
+export function setCodexMicroSlots(
+  slots: ReadonlyArray<CodexMicroSlot>,
+  selectedSlot: number | null,
+) {
+  if (slots === snapshot.slots && selectedSlot === snapshot.selectedSlot) return;
+  update({ slots, selectedSlot });
+}
+
+export const isCodexMicroAvailable = () => bridge() !== undefined;
```

## apps/web/src/components/desktop/CodexMicroCoordinator.tsx

Status: added; +284; −0

```diff
@@ -1,0 +1,284 @@
+import { useAtomValue } from "@effect/atom-react";
+import { scopeThreadRef } from "@t3tools/client-runtime/environment";
+import type {
+  CodexMicroAction,
+  CodexMicroControlId,
+  CodexMicroInputEvent,
+} from "@t3tools/contracts";
+import { useNavigate, useParams } from "@tanstack/react-router";
+import { useEffect, useEffectEvent, useRef, useState } from "react";
+
+import {
+  codexMicroJoystickControl,
+  codexMicroShortcutEvent,
+  resolveCodexMicroControl,
+} from "../../codexMicro/codexMicroActions";
+import { assignCodexMicroSlots, type CodexMicroSlot } from "../../codexMicro/codexMicroSlots";
+import {
+  isCodexMicroAvailable,
+  readCodexMicroSnapshot,
+  setCodexMicroSlots,
+  subscribeCodexMicroInput,
+  useCodexMicroConnected,
+} from "../../codexMicro/codexMicroStore";
+import { useClientSettings } from "../../hooks/useSettings";
+import { usePrimaryEnvironmentId } from "../../state/environments";
+import { useQuietThreadShells } from "../../state/entities";
+import { primaryServerKeybindingsAtom } from "../../state/server";
+import { threadEnvironment } from "../../state/threads";
+import { useOrchestrationCommand } from "../../state/use-orchestration-command";
+import { buildThreadRouteParams } from "../../threadRoutes";
+import type { SidebarThreadSummary } from "../../types";
+import { toastManager } from "../ui/toast";
+
+const AGENT_KEY = /^AG0([0-5])$/;
+const JOYSTICK_TRIGGER = 0.5;
+const JOYSTICK_RELEASE = 0.2;
+// Codex's timings: a second tap within this window focuses the app, and
+// holding the knob this long opens its settings.
+const DOUBLE_TAP_MS = 350;
+const KNOB_LONG_PRESS_MS = 500;
+
+const hasApproval = (thread: SidebarThreadSummary | undefined): thread is SidebarThreadSummary =>
+  thread !== undefined &&
+  thread.hasPendingApprovals &&
+  thread.source.pendingRuntimeRequest !== null;
+const isWorking = (thread: SidebarThreadSummary) =>
+  thread.runtime !== null &&
+  ["preparing", "queued", "starting", "running", "waiting"].includes(thread.runtime.status);
+
+const showUnavailable = () =>
+  toastManager.add({ type: "warning", title: "This action isn't available here" });
+
+/**
+ * Drives a connected Codex Micro: agent keys mirror the six most relevant
+ * threads, other controls run T3 commands. Desktop only; the main process
+ * owns the HID connection.
+ */
+export function CodexMicroCoordinator() {
+  const connected = useCodexMicroConnected();
+  return isCodexMicroAvailable() && connected ? <CodexMicroController /> : null;
+}
+
+function useWindowFocused() {
+  const [focused, setFocused] = useState(() => document.hasFocus());
+  useEffect(() => {
+    const update = () => setFocused(document.hasFocus());
+    window.addEventListener("focus", update);
+    window.addEventListener("blur", update);
+    return () => {
+      window.removeEventListener("focus", update);
+      window.removeEventListener("blur", update);
+    };
+  }, []);
+  return focused;
+}
+
+function CodexMicroController() {
+  const navigate = useNavigate();
+  const settings = useClientSettings((current) => current.codexMicro);
+  const environmentId = usePrimaryEnvironmentId();
+  const routeThreadId = useParams({ strict: false }).threadId ?? null;
+  const windowFocused = useWindowFocused();
+  const shells = useQuietThreadShells();
+  const keybindings = useAtomValue(primaryServerKeybindingsAtom);
+  const respondToApproval = useOrchestrationCommand(threadEnvironment.respondToApproval);
+  const interruptTurn = useOrchestrationCommand(threadEnvironment.interruptTurn);
+  const joystickArmed = useRef(true);
+  const lastAgentTap = useRef<{ index: number; at: number } | null>(null);
+  const knobHold = useRef<{ timer: ReturnType<typeof setTimeout>; fired: boolean } | null>(null);
+
+  const threads = shells.filter((thread) => thread.environmentId === environmentId);
+  const threadsById = new Map(threads.map((thread) => [thread.id as string, thread]));
+
+  // Like Codex, the selected key is the thread open in T3 Code, and a thread
+  // you're looking at no longer reads as unread.
+  useEffect(() => {
+    const current = readCodexMicroSnapshot();
+    const assigned = assignCodexMicroSlots({
+      threads,
+      previous: current.slots,
+      mode: settings.agentMode,
+      pinnedThreads: settings.pinnedThreads,
+      now: new Date().toISOString(),
+    });
+    const next = assigned.map((slot): CodexMicroSlot =>
+      slot.threadId === routeThreadId && windowFocused && slot.status === "done"
+        ? { ...slot, status: "idle" }
+        : slot,
+    );
+    const selected = next.find((slot) => slot.threadId !== null && slot.threadId === routeThreadId);
+    const changed = next.some(
+      (slot, index) =>
+        slot.threadId !== current.slots[index]?.threadId ||
+        slot.status !== current.slots[index]?.status ||
+        slot.title !== current.slots[index]?.title,
+    );
+    setCodexMicroSlots(changed ? next : current.slots, selected?.index ?? null);
+    const snapshot = readCodexMicroSnapshot();
+    void window.desktopBridge?.codexMicro?.setLighting({
+      slots: snapshot.slots.map((slot) => ({
+        status: slot.status,
+        selected: slot.index === snapshot.selectedSlot,
+      })),
+      brightness: settings.brightness,
+      autoDimMs: settings.autoDimMs,
+      keysBacklight: settings.keysBacklight,
+    });
+  }, [threads, routeThreadId, windowFocused, settings]);
+
+  const openSlot = (index: number, focus: boolean) => {
+    const slot = readCodexMicroSnapshot().slots[index];
+    if (!slot?.threadId || !slot.environmentId) return;
+    if (focus) void window.desktopBridge?.revealWindow?.();
+    void navigate({
+      to: "/$environmentId/$threadId",
+      params: buildThreadRouteParams(scopeThreadRef(slot.environmentId, slot.threadId)),
+    });
+  };
+
+  const tapAgentKey = (index: number) => {
+    const now = Date.now();
+    const previous = lastAgentTap.current;
+    const doubleTap = previous?.index === index && now - previous.at <= DOUBLE_TAP_MS;
+    lastAgentTap.current = doubleTap ? null : { index, at: now };
+    openSlot(index, settings.singleTapFocus || doubleTap);
+  };
+
+  const openNeighbor = (step: 1 | -1) => {
+    const snapshot = readCodexMicroSnapshot();
+    const filled = snapshot.slots.filter((slot) => slot.threadId).map((slot) => slot.index);
+    if (filled.length === 0) return;
+    const position = snapshot.selectedSlot === null ? -1 : filled.indexOf(snapshot.selectedSlot);
+    const next =
+      position === -1
+        ? filled[step === 1 ? 0 : filled.length - 1]!
+        : filled[(position + step + filled.length) % filled.length]!;
+    openSlot(next, false);
+  };
+
+  // Approvals and stop only act on threads you can see on the keys.
+  const keyThreads = () => {
+    const snapshot = readCodexMicroSnapshot();
+    const selectedId =
+      snapshot.selectedSlot === null ? null : snapshot.slots[snapshot.selectedSlot]?.threadId;
+    const ids = [selectedId, ...snapshot.slots.map((slot) => slot.threadId)];
+    return ids.flatMap((id) => {
+      const thread = id ? threadsById.get(id) : undefined;
+      return thread ? [thread] : [];
+    });
+  };
+
+  const runAction = (action: CodexMicroAction) => {
+    switch (action.type) {
+      case "none":
+        return;
+      case "nextSlot":
+        return openNeighbor(1);
+      case "previousSlot":
+        return openNeighbor(-1);
+      case "focusApp":
+        void window.desktopBridge?.revealWindow?.();
+        return;
+      case "approve":
+      case "approveForSession":
+      case "decline": {
+        const target = keyThreads().find(hasApproval);
+        const request = target?.source.pendingRuntimeRequest;
+        if (!target || !request) return showUnavailable();
+        void respondToApproval({
+          environmentId: target.environmentId,
+          input: {
+            threadId: target.id,
+            requestId: request.id,
+            decision:
+              action.type === "approve"
+                ? "accept"
+                : action.type === "decline"
+                  ? "decline"
+                  : "acceptForSession",
+          },
+        });
+        return;
+      }
+      case "stop": {
+        const [selected, ...others] = keyThreads();
+        const working = [selected, ...others].filter(
+          (thread): thread is SidebarThreadSummary => thread !== undefined && isWorking(thread),
+        );
+        // With several running threads, stop only the open one.
+        const target =
+          selected && routeThreadId === selected.id && isWorking(selected)
+            ? selected
+            : new Set(working.map((thread) => thread.id)).size === 1
+              ? working[0]
+              : undefined;
+        if (!target) return showUnavailable();
+        void interruptTurn({ environmentId: target.environmentId, input: { threadId: target.id } });
+        return;
+      }
+      case "command": {
+        const init = codexMicroShortcutEvent(keybindings, action.command, navigator.platform);
+        if (!init) return showUnavailable();
+        const target =
+          document.activeElement instanceof HTMLElement ? document.activeElement : document.body;
+        target.dispatchEvent(new KeyboardEvent("keydown", init));
+        return;
+      }
+    }
+  };
+
+  const runControl = (control: CodexMicroControlId) =>
+    runAction(resolveCodexMicroControl(control, settings.knobMode, settings.controls));
+
+  // Holding the knob opens these settings; a short press runs its click action.
+  const handleKnobPress = (act: number | null) => {
+    if (act === 1 && knobHold.current === null) {
+      const hold = {
+        fired: false,
+        timer: setTimeout(() => {
+          hold.fired = true;
+          void navigate({ to: "/settings/codex-micro" });
+        }, KNOB_LONG_PRESS_MS),
+      };
+      knobHold.current = hold;
+    } else if (act === 0 && knobHold.current !== null) {
+      const hold = knobHold.current;
+      knobHold.current = null;
+      clearTimeout(hold.timer);
+      if (!hold.fired) runControl("ENC");
+    }
+  };
+
+  const handleInput = useEffectEvent((input: CodexMicroInputEvent) => {
+    if (input.type === "joystick") {
+      if (input.distance < JOYSTICK_RELEASE) joystickArmed.current = true;
+      else if (joystickArmed.current && input.distance >= JOYSTICK_TRIGGER) {
+        joystickArmed.current = false;
+        runControl(codexMicroJoystickControl(input.angle));
+      }
+      return;
+    }
+    const agent = AGENT_KEY.exec(input.key);
+    if (agent) {
+      if (input.act === 1) tapAgentKey(Number(agent[1]));
+    } else if (input.key === "ENC_CW" || input.key === "ENC_CC") {
+      if (input.act === 2) runControl(input.key);
+    } else if (input.key.startsWith("ENC")) {
+      handleKnobPress(input.act);
+    } else if (input.act === 1 && input.key in settings.controls) {
+      // One wide keycap sits on ACT10 + ACT11; like Codex, ignore the second switch.
+      if (input.key === "ACT11" && !settings.separateMicrophoneKeys) return;
+      runControl(input.key as CodexMicroControlId);
+    }
+  });
+
+  useEffect(() => {
+    const unsubscribe = subscribeCodexMicroInput(handleInput);
+    return () => {
+      unsubscribe();
+      if (knobHold.current) clearTimeout(knobHold.current.timer);
+    };
+  }, []);
+  return null;
+}
```

## apps/web/src/components/settings/CodexMicroDevicePreview.tsx

Status: added; +239; −0

```diff
@@ -1,0 +1,239 @@
+import {
+  CODEX_MICRO_STATUS_COLORS,
+  type CodexMicroAction,
+  type CodexMicroControlId,
+} from "@t3tools/contracts";
+import {
+  AppWindowIcon,
+  ArrowDownIcon,
+  ArrowLeftIcon,
+  ArrowRightIcon,
+  ArrowUpIcon,
+  CheckCheckIcon,
+  CheckCircle2Icon,
+  ChevronLeftIcon,
+  ChevronRightIcon,
+  CircleSlashIcon,
+  CommandIcon,
+  GitCompareIcon,
+  KeyboardIcon,
+  PanelLeftIcon,
+  SquareIcon,
+  SquarePenIcon,
+  TerminalIcon,
+  XCircleIcon,
+} from "lucide-react";
+import type { CSSProperties, ReactElement, ReactNode } from "react";
+
+import type { CodexMicroSlot } from "../../codexMicro/codexMicroSlots";
+import { describeCodexMicroAction } from "../../codexMicro/codexMicroActions";
+import { cn } from "../../lib/utils";
+import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
+
+const COMMAND_ICONS: Partial<Record<string, (className: string) => ReactNode>> = {
+  "chat.new": (className) => <SquarePenIcon className={className} />,
+  "chat.newLocal": (className) => <SquarePenIcon className={className} />,
+  "terminal.toggle": (className) => <TerminalIcon className={className} />,
+  "diff.toggle": (className) => <GitCompareIcon className={className} />,
+  "commandPalette.toggle": (className) => <CommandIcon className={className} />,
+  "thread.previous": (className) => <ArrowUpIcon className={className} />,
+  "thread.next": (className) => <ArrowDownIcon className={className} />,
+  "navigation.back": (className) => <ChevronLeftIcon className={className} />,
+  "navigation.forward": (className) => <ChevronRightIcon className={className} />,
+  "sidebar.toggle": (className) => <PanelLeftIcon className={className} />,
+};
+
+export function CodexMicroActionIcon(props: {
+  readonly action: CodexMicroAction;
+  readonly className?: string;
+}) {
+  const className = props.className ?? "size-4";
+  switch (props.action.type) {
+    case "approve":
+      return <CheckCircle2Icon className={className} />;
+    case "approveForSession":
+      return <CheckCheckIcon className={className} />;
+    case "decline":
+      return <XCircleIcon className={className} />;
+    case "stop":
+      return <SquareIcon className={className} />;
+    case "nextSlot":
+      return <ArrowRightIcon className={className} />;
+    case "previousSlot":
+      return <ArrowLeftIcon className={className} />;
+    case "focusApp":
+      return <AppWindowIcon className={className} />;
+    case "none":
+      return <CircleSlashIcon className={className} />;
+    case "command":
+      return (
+        COMMAND_ICONS[props.action.command]?.(className) ?? <KeyboardIcon className={className} />
+      );
+  }
+}
+
+const hex = (color: number) => `#${color.toString(16).padStart(6, "0")}`;
+
+const KEY_BASE =
+  "relative z-10 aspect-square w-full cursor-pointer rounded-xl outline-none transition-transform duration-150 focus-visible:ring-2 focus-visible:ring-micro-glow focus-visible:ring-inset";
+const CAP =
+  "absolute inset-0 flex items-center justify-center rounded-xl border border-white/90 bg-micro-cap text-foreground shadow-micro-cap dark:border-white/15";
+
+function Key(props: {
+  readonly label: string;
+  readonly className?: string;
+  readonly onClick: () => void;
+  readonly children: ReactNode;
+}): ReactElement {
+  return (
+    <Tooltip>
+      <TooltipTrigger
+        render={
+          <button
+            type="button"
+            aria-label={props.label}
+            className={cn(KEY_BASE, props.className)}
+            onClick={props.onClick}
+          />
+        }
+      >
+        {props.children}
+      </TooltipTrigger>
+      <TooltipPopup>{props.label}</TooltipPopup>
+    </Tooltip>
+  );
+}
+
+interface PreviewProps {
+  readonly slots: ReadonlyArray<CodexMicroSlot>;
+  readonly selectedSlot: number | null;
+  readonly pinnedThreads: ReadonlyArray<string | null>;
+  readonly pressedKeys: ReadonlySet<string>;
+  readonly joystick: { readonly angle: number; readonly distance: number };
+  readonly separateMicrophoneKeys: boolean;
+  readonly actionFor: (control: CodexMicroControlId) => CodexMicroAction;
+  readonly onAgentKey: (index: number) => void;
+  readonly onControl: (control: CodexMicroControlId) => void;
+}
+
+/** Interactive picture of the device, styled after Codex's own preview. */
+export function CodexMicroDevicePreview(props: PreviewProps) {
+  const commandKey = (control: CodexMicroControlId, wide = false) => {
+    const action = props.actionFor(control);
+    const pressed = props.pressedKeys.has(control) || (wide && props.pressedKeys.has("ACT11"));
+    return (
+      <Key
+        key={control}
+        label={describeCodexMicroAction(action)}
+        className={cn(wide && "col-span-2 aspect-auto", pressed && "translate-y-px scale-[0.985]")}
+        onClick={() => props.onControl(control)}
+      >
+        <span className={CAP}>
+          <CodexMicroActionIcon action={action} className="size-5" />
+        </span>
+      </Key>
+    );
+  };
+
+  const agentKey = (index: number) => {
+    const slot = props.slots[index];
+    const status = slot?.status ?? "off";
+    const selected = props.selectedSlot === index;
+    const pressed = props.pressedKeys.has(`AG0${index}`);
+    const color = status === "off" ? "transparent" : hex(CODEX_MICRO_STATUS_COLORS[status]);
+    const glow: CSSProperties = {
+      backgroundColor: color,
+      opacity: status === "off" ? 0 : pressed ? 0.45 : selected ? 0.4 : 0.25,
+    };
+    const tint: CSSProperties = {
+      backgroundColor: color,
+      opacity: status === "off" ? 0 : pressed ? 0.65 : selected ? 0.55 : 0.4,
+    };
+    return (
+      <Key
+        key={`AG0${index}`}
+        label={slot?.threadId ? (slot.title ?? "Untitled thread") : `Agent key ${index + 1}`}
+        className={cn(pressed && "translate-y-px scale-[0.985]")}
+        onClick={() => props.onAgentKey(index)}
+      >
+        <span
+          className="pointer-events-none absolute -inset-[5%] rounded-xl blur-sm transition-[background-color,opacity] duration-200"
+          style={glow}
+        />
+        <span className="absolute inset-0 overflow-hidden rounded-xl border border-white/80 bg-white/45 shadow-micro-agent dark:border-white/15 dark:bg-white/10">
+          <span
+            className="absolute inset-0 transition-[background-color,opacity] duration-200"
+            style={tint}
+          />
+          <span className="absolute inset-0 bg-gradient-to-br from-white/40 via-white/10 to-transparent dark:from-white/15 dark:via-white/5" />
+          <span
+            className={cn(
+              "absolute top-1/2 left-1/2 size-3 -translate-x-1/2 -translate-y-1/2 rounded-full bg-micro-dot shadow-micro-dot transition-transform",
+              pressed && "scale-110",
+            )}
+          />
+          {props.pinnedThreads[index] ? (
+            <span className="absolute top-1.5 right-1.5 size-1.5 rounded-full bg-foreground/50" />
+          ) : null}
+        </span>
+      </Key>
+    );
+  };
+
+  const knobPressed = [...props.pressedKeys].some((key) => key.startsWith("ENC"));
+  const angle = props.joystick.angle * Math.PI * 2;
+  const offset = Math.min(props.joystick.distance, 1) * 9;
+  const stickStyle: CSSProperties = {
+    transform: `translate(${Math.cos(angle) * offset}px, ${Math.sin(angle) * offset}px)`,
+  };
+
+  return (
+    <div className="relative grid aspect-square w-full max-w-68 grid-cols-4 grid-rows-4 gap-1 rounded-xl border border-white/80 bg-micro-body p-2.5 shadow-micro-body dark:border-white/10">
+      <div className="pointer-events-none absolute top-13 right-2.5 left-14 h-22 rounded-full bg-micro-glow/30 blur-2xl" />
+      <Key label="Knob" className="rounded-full" onClick={() => props.onControl("ENC")}>
+        <span
+          className={cn(
+            "pointer-events-none absolute -inset-0.5 rounded-full border border-transparent transition-[border-color,box-shadow]",
+            knobPressed && "border-micro-glow/70 shadow-micro-ring",
+          )}
+        />
+        <span className="absolute inset-[4%] overflow-hidden rounded-full bg-micro-knob shadow-micro-knob">
+          <span className="absolute -top-4 left-3 h-24 w-12 rotate-45 bg-white/50 dark:bg-white/10" />
+        </span>
+      </Key>
+      {agentKey(0)}
+      {agentKey(1)}
+      <Key label="Analog stick" onClick={() => props.onControl("JOY_UP")}>
+        <span className="absolute inset-0 flex items-center justify-center rounded-xl border border-white/80 bg-micro-stick-housing shadow-micro-cap dark:border-white/10">
+          <span
+            className="size-[72%] rounded-full bg-micro-stick shadow-micro-stick transition-transform duration-75"
+            style={stickStyle}
+          />
+        </span>
+      </Key>
+      {agentKey(2)}
+      {agentKey(3)}
+      {agentKey(4)}
+      {agentKey(5)}
+      {commandKey("ACT06")}
+      {commandKey("ACT07")}
+      {commandKey("ACT08")}
+      {commandKey("ACT09")}
+      <div
+        aria-hidden
+        className="relative z-10 flex aspect-square items-center justify-center gap-1"
+      >
+        <div className="flex flex-col gap-0.5">
+          <span className="size-1 rounded-full bg-micro-led-active" />
+          <span className="size-1 rounded-full bg-micro-led" />
+          <span className="size-1 rounded-full bg-micro-led" />
+        </div>
+        <div className="size-[58%] rounded-full bg-micro-touch shadow-micro-cap" />
+      </div>
+      {props.separateMicrophoneKeys
+        ? [commandKey("ACT10"), commandKey("ACT11")]
+        : commandKey("ACT10", true)}
+      {commandKey("ACT12")}
+    </div>
+  );
+}
```

## apps/web/src/components/settings/CodexMicroSettings.tsx

Status: added; +474; −0

```diff
@@ -1,0 +1,474 @@
+import {
+  CODEX_MICRO_STATUS_COLORS,
+  DEFAULT_CODEX_MICRO_CONTROLS,
+  type CodexMicroAction,
+  type CodexMicroAgentMode,
+  type CodexMicroControlId,
+  type CodexMicroKnobMode,
+  type CodexMicroSettings,
+} from "@t3tools/contracts";
+import { Navigate } from "@tanstack/react-router";
+import type { ReactNode } from "react";
+
+import {
+  CODEX_MICRO_ACTION_GROUPS,
+  codexMicroActionKey,
+  describeCodexMicroAction,
+  resolveCodexMicroControl,
+} from "../../codexMicro/codexMicroActions";
+import { useCodexMicroSnapshot } from "../../codexMicro/codexMicroStore";
+import {
+  getClientSettings,
+  updateClientSettings,
+  useClientSettings,
+} from "../../hooks/useSettings";
+import { usePrimaryEnvironmentId } from "../../state/environments";
+import { useQuietThreadShells } from "../../state/entities";
+import { Button } from "../ui/button";
+import {
+  Menu,
+  MenuGroup,
+  MenuGroupLabel,
+  MenuPopup,
+  MenuRadioGroup,
+  MenuRadioItem,
+  MenuSeparator,
+  MenuTrigger,
+} from "../ui/menu";
+import { SelectButton } from "../ui/select";
+import { Switch } from "../ui/switch";
+import { CodexMicroActionIcon, CodexMicroDevicePreview } from "./CodexMicroDevicePreview";
+import { SettingsPageContainer, SettingsRow, SettingsSection } from "./settingsLayout";
+
+const AGENT_MODES: ReadonlyArray<{
+  value: CodexMicroAgentMode;
+  label: string;
+  description: string;
+}> = [
+  {
+    value: "priority",
+    label: "Priority threads",
+    description: "Waiting, unread, and active threads first",
+  },
+  { value: "pinned", label: "Pinned threads", description: "First six threads in Pinned" },
+  {
+    value: "recent",
+    label: "Most recent threads",
+    description: "First six recently updated threads",
+  },
+];
+
+const KNOB_MODES: ReadonlyArray<{ value: CodexMicroKnobMode; label: string; description: string }> =
+  [
+    {
+      value: "agents",
+      label: "Agent keys",
+      description: "Turn to choose an agent key, click to open its thread",
+    },
+    {
+      value: "threads",
+      label: "Thread switching",
+      description: "Turn to move between threads, click for the command palette",
+    },
+    { value: "custom", label: "Custom", description: "Choose an action for each turn and click" },
+  ];
+
+const BRIGHTNESS = [0.25, 0.5, 0.8, 1] as const;
+const AUTO_DIM: ReadonlyArray<{ value: number | null; label: string }> = [
+  { value: null, label: "Off" },
+  { value: 60_000, label: "1 minute" },
+  { value: 180_000, label: "3 minutes" },
+  { value: 600_000, label: "10 minutes" },
+  { value: 1_800_000, label: "30 minutes" },
+  { value: 3_600_000, label: "1 hour" },
+];
+
+const STATUS_LEGEND = [
+  ["working", "Working"],
+  ["needs-input", "Needs you"],
+  ["done", "Unread"],
+  ["error", "Error"],
+  ["idle", "Idle"],
+] as const;
+
+const COMMAND_KEY_ROWS: ReadonlyArray<[CodexMicroControlId, string, string]> = [
+  ["ACT06", "Key 1", "Third row, left"],
+  ["ACT07", "Key 2", "Third row"],
+  ["ACT08", "Key 3", "Third row"],
+  ["ACT09", "Key 4", "Third row, right"],
+  ["ACT10", "Wide key", "Bottom row"],
+  ["ACT11", "Wide key, right switch", "Bottom row"],
+  ["ACT12", "Corner key", "Bottom row, right"],
+];
+const STICK: ReadonlyArray<[CodexMicroControlId, string]> = [
+  ["JOY_UP", "Up"],
+  ["JOY_RIGHT", "Right"],
+  ["JOY_DOWN", "Down"],
+  ["JOY_LEFT", "Left"],
+];
+const KNOB: ReadonlyArray<[CodexMicroControlId, string]> = [
+  ["ENC_CC", "Turn left"],
+  ["ENC_CW", "Turn right"],
+  ["ENC", "Click"],
+];
+
+const controlRowId = (control: CodexMicroControlId) => `codex-micro-${control}`;
+const agentRowId = (index: number) => `codex-micro-agent-${index}`;
+
+function openRowMenu(id: string) {
+  const row = document.getElementById(id);
+  row?.scrollIntoView({ block: "center", behavior: "smooth" });
+  row?.querySelector<HTMLButtonElement>("[data-codex-micro-trigger]")?.click();
+}
+
+// Read the latest stored value so quick successive edits don't overwrite each other.
+const save = (patch: Partial<CodexMicroSettings>) =>
+  void updateClientSettings({ codexMicro: { ...getClientSettings().codexMicro, ...patch } });
+
+function OptionMenu<T>(props: {
+  readonly label: string;
+  readonly value: T;
+  readonly options: ReadonlyArray<{ value: T; label: string }>;
+  readonly onChange: (value: T) => void;
+}) {
+  const index = Math.max(
+    0,
+    props.options.findIndex((option) => option.value === props.value),
+  );
+  return (
+    <Menu>
+      <MenuTrigger
+        data-codex-micro-trigger
+        aria-label={props.label}
+        render={<SelectButton size="sm" />}
+        className="w-auto min-w-0"
+      >
+        {props.options[index]?.label}
+      </MenuTrigger>
+      <MenuPopup align="end">
+        <MenuRadioGroup
+          value={String(index)}
+          onValueChange={(next) => props.onChange(props.options[Number(next)]!.value)}
+        >
+          {props.options.map((option, i) => (
+            <MenuRadioItem key={`${i}:${option.label}`} closeOnClick value={String(i)}>
+              {option.label}
+            </MenuRadioItem>
+          ))}
+        </MenuRadioGroup>
+      </MenuPopup>
+    </Menu>
+  );
+}
+
+function ActionMenu(props: {
+  readonly action: CodexMicroAction;
+  readonly onChange: (action: CodexMicroAction) => void;
+}) {
+  const options = new Map(
+    CODEX_MICRO_ACTION_GROUPS.flatMap((group) => group.options).map((option) => [
+      option.key,
+      option.action,
+    ]),
+  );
+  return (
+    <Menu>
+      <MenuTrigger
+        data-codex-micro-trigger
+        aria-label={`Action: ${describeCodexMicroAction(props.action)}`}
+        render={<SelectButton size="sm" />}
+        className="w-auto min-w-0 max-w-60"
+      >
+        <span className="flex min-w-0 items-center gap-1.5">
+          <CodexMicroActionIcon action={props.action} className="size-3.5 shrink-0 opacity-70" />
+          <span className="truncate">{describeCodexMicroAction(props.action)}</span>
+        </span>
+      </MenuTrigger>
+      <MenuPopup align="end" className="max-h-96 overflow-y-auto">
+        <MenuRadioGroup
+          value={codexMicroActionKey(props.action)}
+          onValueChange={(key) => {
+            const action = options.get(String(key));
+            if (action) props.onChange(action);
+          }}
+        >
+          {CODEX_MICRO_ACTION_GROUPS.map((group, index) => (
+            <MenuGroup key={group.title}>
+              {index > 0 ? <MenuSeparator /> : null}
+              <MenuGroupLabel>{group.title}</MenuGroupLabel>
+              {group.options.map((option) => (
+                <MenuRadioItem key={option.key} closeOnClick value={option.key}>
+                  {option.label}
+                </MenuRadioItem>
+              ))}
+            </MenuGroup>
+          ))}
+        </MenuRadioGroup>
+      </MenuPopup>
+    </Menu>
+  );
+}
+
+export function CodexMicroSettingsPanel() {
+  const settings = useClientSettings((current) => current.codexMicro);
+  const { loaded, device, slots, selectedSlot, pressedKeys, joystick } = useCodexMicroSnapshot();
+  const environmentId = usePrimaryEnvironmentId();
+  const threads = useQuietThreadShells()
+    .filter(
+      (thread) => thread.environmentId === environmentId && !thread.archivedAt && !thread.deletedAt,
+    )
+    .toSorted((a, b) => b.updatedAt.localeCompare(a.updatedAt))
+    .slice(0, 15);
+
+  // The page only exists while the device is attached.
+  if (!loaded) return null;
+  if (!device.connected) return <Navigate to="/settings/general" replace />;
+
+  const actionFor = (control: CodexMicroControlId) =>
+    resolveCodexMicroControl(control, settings.knobMode, settings.controls);
+  const setControl = (control: CodexMicroControlId, action: CodexMicroAction) =>
+    save({ controls: { ...getClientSettings().codexMicro.controls, [control]: action } });
+  const setPin = (index: number, threadId: string | null) => {
+    const pinnedThreads = Array.from({ length: 6 }, (_, i) => settings.pinnedThreads[i] ?? null);
+    // A thread lives on one key at a time.
+    for (let i = 0; i < pinnedThreads.length; i += 1) {
+      if (threadId !== null && pinnedThreads[i] === threadId) pinnedThreads[i] = null;
+    }
+    pinnedThreads[index] = threadId;
+    save({ pinnedThreads });
+  };
+  const controlRow = (control: CodexMicroControlId, title: ReactNode, description?: string) => (
+    <SettingsRow
+      key={control}
+      id={controlRowId(control)}
+      title={title}
+      description={description}
+      control={
+        <ActionMenu
+          action={settings.controls[control]}
+          onChange={(action) => setControl(control, action)}
+        />
+      }
+    />
+  );
+  const battery =
+    device.batteryPercentage === null
+      ? null
+      : `${device.batteryPercentage}%${device.charging ? ", charging" : ""}`;
+
+  return (
+    <SettingsPageContainer>
+      <div className="flex flex-col items-center gap-4 rounded-2xl border bg-muted/30 px-4 pt-7 pb-5">
+        <CodexMicroDevicePreview
+          slots={slots}
+          selectedSlot={selectedSlot}
+          pinnedThreads={settings.pinnedThreads}
+          pressedKeys={pressedKeys}
+          joystick={joystick}
+          separateMicrophoneKeys={settings.separateMicrophoneKeys}
+          actionFor={actionFor}
+          onAgentKey={(index) => openRowMenu(agentRowId(index))}
+          onControl={(control) =>
+            openRowMenu(
+              control === "ENC" && settings.knobMode !== "custom"
+                ? "codex-micro-knob-mode"
+                : controlRowId(control),
+            )
+          }
+        />
+        <p className="text-xs text-muted-foreground">Click a key to change what it does.</p>
+        <div className="flex flex-wrap justify-center gap-x-4 gap-y-1 text-xs text-muted-foreground">
+          {STATUS_LEGEND.map(([status, label]) => (
+            <span key={status} className="inline-flex items-center gap-1.5">
+              <span
+                className="size-2 rounded-full border border-black/10 dark:border-white/15"
+                style={{
+                  backgroundColor: `#${CODEX_MICRO_STATUS_COLORS[status].toString(16).padStart(6, "0")}`,
+                }}
+              />
+              {label}
+            </span>
+          ))}
+        </div>
+      </div>
+
+      <SettingsSection id="codex-micro-device" title="Device">
+        <SettingsRow
+          title="Codex Micro"
+          description={[
+            device.transport === "bluetooth"
+              ? "Connected over Bluetooth"
+              : device.transport === "usb"
+                ? "Connected over USB"
+                : "Connected",
+            device.firmwareVersion ? `firmware ${device.firmwareVersion}` : null,
+          ]
+            .filter(Boolean)
+            .join(", ")}
+          control={
+            battery ? <span className="text-sm text-muted-foreground">{battery}</span> : null
+          }
+        />
+      </SettingsSection>
+
+      <SettingsSection id="codex-micro-agents" title="Agent keys">
+        <SettingsRow
+          title="Follow"
+          description={AGENT_MODES.find((mode) => mode.value === settings.agentMode)?.description}
+          control={
+            <OptionMenu
+              label="Agent keys follow"
+              value={settings.agentMode}
+              options={AGENT_MODES}
+              onChange={(agentMode) => save({ agentMode })}
+            />
+          }
+        />
+        <SettingsRow
+          title="Focus T3 Code with a single tap"
+          description="Open the thread and bring T3 Code forward with one tap instead of a double tap."
+          control={
+            <Switch
+              checked={settings.singleTapFocus}
+              aria-label="Focus T3 Code with a single tap"
+              onCheckedChange={(singleTapFocus) => save({ singleTapFocus })}
+            />
+          }
+        />
+        {slots.map((slot) => {
+          const pinned = settings.pinnedThreads[slot.index] ?? null;
+          return (
+            <SettingsRow
+              key={slot.index}
+              id={agentRowId(slot.index)}
+              title={`Key ${slot.index + 1}`}
+              description={slot.title ?? "No thread"}
+              control={
+                <OptionMenu
+                  label={`Agent key ${slot.index + 1}`}
+                  value={pinned}
+                  options={[
+                    { value: null, label: "Automatic" },
+                    ...(pinned && !threads.some((thread) => thread.id === pinned)
+                      ? [{ value: pinned, label: slot.title ?? "Pinned thread" }]
+                      : []),
+                    ...threads.map((thread) => ({
+                      value: thread.id as string | null,
+                      label: thread.title,
+                    })),
+                  ]}
+                  onChange={(threadId) => setPin(slot.index, threadId)}
+                />
+              }
+            />
+          );
+        })}
+      </SettingsSection>
+
+      <SettingsSection id="codex-micro-keys" title="Command keys">
+        <SettingsRow
+          title="Use separate microphone keys"
+          description="Map the two switches beneath the wide key separately."
+          control={
+            <Switch
+              checked={settings.separateMicrophoneKeys}
+              aria-label="Use separate microphone keys"
+              onCheckedChange={(separateMicrophoneKeys) => save({ separateMicrophoneKeys })}
+            />
+          }
+        />
+        {COMMAND_KEY_ROWS.filter(
+          ([control]) => control !== "ACT11" || settings.separateMicrophoneKeys,
+        ).map(([control, title, description]) =>
+          controlRow(
+            control,
+            control === "ACT10" && settings.separateMicrophoneKeys
+              ? "Wide key, left switch"
+              : title,
+            description,
+          ),
+        )}
+      </SettingsSection>
+
+      <SettingsSection id="codex-micro-knob" title="Knob">
+        <SettingsRow
+          id="codex-micro-knob-mode"
+          title="Mode"
+          description={KNOB_MODES.find((mode) => mode.value === settings.knobMode)?.description}
+          control={
+            <OptionMenu
+              label="Knob mode"
+              value={settings.knobMode}
+              options={KNOB_MODES}
+              onChange={(knobMode) => save({ knobMode })}
+            />
+          }
+        />
+        {settings.knobMode === "custom"
+          ? KNOB.map(([control, title]) => controlRow(control, title))
+          : null}
+      </SettingsSection>
+
+      <SettingsSection id="codex-micro-stick" title="Analog stick">
+        {STICK.map(([control, title]) => controlRow(control, title))}
+      </SettingsSection>
+
+      <SettingsSection id="codex-micro-lighting" title="Lighting">
+        <SettingsRow
+          title="Brightness"
+          control={
+            <OptionMenu
+              label="Brightness"
+              value={BRIGHTNESS.reduce((best, value) =>
+                Math.abs(value - settings.brightness) < Math.abs(best - settings.brightness)
+                  ? value
+                  : best,
+              )}
+              options={BRIGHTNESS.map((value) => ({ value, label: `${Math.round(value * 100)}%` }))}
+              onChange={(brightness) => save({ brightness })}
+            />
+          }
+        />
+        <SettingsRow
+          title="Auto-dim"
+          description="Turn lighting off after inactivity. It comes back when you use the device or a thread changes state."
+          control={
+            <OptionMenu
+              label="Auto-dim"
+              value={settings.autoDimMs}
+              options={AUTO_DIM}
+              onChange={(autoDimMs) => save({ autoDimMs })}
+            />
+          }
+        />
+        <SettingsRow
+          title="Key backlight"
+          description="Light the command keycaps."
+          control={
+            <Switch
+              checked={settings.keysBacklight}
+              aria-label="Key backlight"
+              onCheckedChange={(keysBacklight) => save({ keysBacklight })}
+            />
+          }
+        />
+      </SettingsSection>
+
+      <SettingsSection id="codex-micro-layout" title="Layout">
+        <SettingsRow
+          title="Reset layout"
+          description="Restore the command keys, knob, and analog stick to their defaults."
+          control={
+            <Button
+              size="xs"
+              variant="outline"
+              onClick={() => save({ controls: DEFAULT_CODEX_MICRO_CONTROLS, knobMode: "agents" })}
+            >
+              Reset
+            </Button>
+          }
+        />
+      </SettingsSection>
+    </SettingsPageContainer>
+  );
+}
```

## apps/web/src/components/settings/SettingsSidebarNav.tsx

Status: modified; +7; −1

```diff
@@ -14,6 +14,7 @@
   BotIcon,
   createLucideIcon,
   CalendarClockIcon,
+  Gamepad2Icon,
   GitBranchIcon,
   HardDriveIcon,
   PanelsTopLeftIcon,
@@ -49,6 +50,7 @@
 } from "./settingsSearch";
 import { useAvailableSettingsSearchItems } from "./useAvailableSettingsSearchItems";
 import { validateSettingsScopeSearch } from "./settingsScope";
+import { useCodexMicroConnected } from "../../codexMicro/codexMicroStore";
 
 const SnapShotIcon = createLucideIcon("snap-shot", [
   [
@@ -84,6 +86,7 @@
   "/settings/snap-shot": SnapShotIcon,
   "/settings/providers": BotIcon,
   "/settings/integrations": BlocksIcon,
+  "/settings/codex-micro": Gamepad2Icon,
   "/settings/scheduled-tasks": CalendarClockIcon,
   "/settings/source-control": GitBranchIcon,
   "/settings/storage": HardDriveIcon,
@@ -114,8 +117,11 @@
   const currentHash = useLocation({ select: (location) => location.hash });
   const currentSearch = useLocation({ select: (location) => location.search });
   const scopeSearch = validateSettingsScopeSearch(currentSearch);
+  const codexMicroConnected = useCodexMicroConnected();
   const navItems = SETTINGS_NAV_ITEMS.filter(
-    (item) => item.to !== "/settings/projects" || isSettingsOverviewVisible(scopeSearch),
+    (item) =>
+      (item.to !== "/settings/projects" || isSettingsOverviewVisible(scopeSearch)) &&
+      (item.to !== "/settings/codex-micro" || codexMicroConnected),
   );
   const { isMobile, setOpenMobile, open, setOpen } = useSidebar();
   const searchInputRef = useRef<HTMLInputElement>(null);
```

## apps/web/src/components/settings/settingsSearch.ts

Status: modified; +3; −0

```diff
@@ -25,6 +25,7 @@
   | "/settings/snap-shot"
   | "/settings/providers"
   | "/settings/integrations"
+  | "/settings/codex-micro"
   | "/settings/scheduled-tasks"
   | "/settings/source-control"
   | "/settings/storage"
@@ -84,6 +85,7 @@
   "/settings/snap-shot": "SnapShots",
   "/settings/providers": "Providers",
   "/settings/integrations": "Integrations",
+  "/settings/codex-micro": "Codex Micro",
   "/settings/scheduled-tasks": "Scheduled Tasks",
   "/settings/source-control": "Source Control",
   "/settings/storage": "Storage",
@@ -1050,6 +1052,7 @@
   "/settings/keybindings": null,
   "/settings/providers": null,
   "/settings/integrations": null,
+  "/settings/codex-micro": null,
   "/settings/source-control": "environment-defaults",
   "/settings/storage": "project-defaults",
   "/settings/connections": "connections",
```

## apps/web/src/index.css

Status: modified; +56; −0

```diff
@@ -192,6 +192,24 @@
   --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
   /* The composer's elevation: a soft drop pulled in under the surface, shared
      by the composer, its context strip and attached banners. */
+  /* Codex Micro preview: the hardware's own materials (see :root below). */
+  --color-micro-body: var(--micro-body);
+  --color-micro-cap: var(--micro-cap);
+  --color-micro-knob: var(--micro-knob);
+  --color-micro-stick-housing: var(--micro-stick-housing);
+  --color-micro-stick: var(--micro-stick);
+  --color-micro-touch: var(--micro-touch);
+  --color-micro-dot: var(--micro-dot);
+  --color-micro-glow: var(--micro-glow);
+  --color-micro-led: var(--micro-led);
+  --color-micro-led-active: var(--micro-led-active);
+  --shadow-micro-body: var(--micro-shadow-body);
+  --shadow-micro-cap: var(--micro-shadow-cap);
+  --shadow-micro-agent: var(--micro-shadow-agent);
+  --shadow-micro-knob: var(--micro-shadow-knob);
+  --shadow-micro-stick: var(--micro-shadow-stick);
+  --shadow-micro-dot: var(--micro-shadow-dot);
+  --shadow-micro-ring: 0 0 0 1px rgb(141 181 255 / 18%), 0 0 14px rgb(141 181 255 / 38%);
   --shadow-composer: 0 12px 28px -18px rgb(0 0 0 / 40%);
   --shadow-composer-dark: 0 14px 32px -18px rgb(0 0 0 / 75%);
   /* Overlap the lower toolbar backdrop by one pixel to cover the composer seam. */
@@ -2482,3 +2500,41 @@
     }
   }
 }
+
+/* Codex Micro preview materials, matching the device and Codex's own preview. */
+:root {
+  --micro-body: #ebe8e2;
+  --micro-cap: #f7f5f1;
+  --micro-knob: #f5f3ee;
+  --micro-stick-housing: #dad4ca;
+  --micro-stick: #111111;
+  --micro-touch: #2d2925;
+  --micro-dot: rgb(104 95 174 / 85%);
+  --micro-glow: #8db5ff;
+  --micro-led: #b8b98b;
+  --micro-led-active: #9ebdff;
+  --micro-shadow-body: inset 0 0 0 1px rgb(255 255 255 / 72%), 0 1px 0 rgb(255 255 255 / 65%);
+  --micro-shadow-cap: inset 0 0 0 1px rgb(228 228 228 / 68%), 0 1px 2px rgb(79 72 63 / 18%);
+  --micro-shadow-agent:
+    inset 0 0 0 1px rgb(228 228 228 / 40%), inset 0 1px 2px rgb(255 255 255 / 50%),
+    0 1px 2px rgb(79 72 63 / 16%);
+  --micro-shadow-knob: inset 0 -1px 2px rgb(92 84 72 / 16%), 0 1px 2px rgb(89 80 67 / 18%);
+  --micro-shadow-stick: inset 0 -1px 2px rgb(255 255 255 / 12%), 0 1px 2px rgb(44 40 35 / 28%);
+  --micro-shadow-dot: 0 0 5px rgb(87 76 151 / 42%);
+
+  @variant dark {
+    --micro-body: #18191e;
+    --micro-cap: #202127;
+    --micro-knob: #262831;
+    --micro-stick-housing: #23252c;
+    --micro-touch: #202127;
+    --micro-dot: rgb(129 119 200 / 90%);
+    --micro-shadow-body: inset 0 0 0 1px rgb(255 255 255 / 8%), 0 1px 0 rgb(0 0 0 / 28%);
+    --micro-shadow-cap: inset 0 0 0 1px rgb(255 255 255 / 8%), 0 1px 2px rgb(0 0 0 / 32%);
+    --micro-shadow-agent:
+      inset 0 0 0 1px rgb(255 255 255 / 9%), inset 0 1px 2px rgb(255 255 255 / 8%),
+      0 1px 2px rgb(0 0 0 / 30%);
+    --micro-shadow-knob: inset 0 -1px 2px rgb(255 255 255 / 8%), 0 1px 2px rgb(0 0 0 / 34%);
+    --micro-shadow-dot: 0 0 6px rgb(118 104 197 / 50%);
+  }
+}
```

[Next page](https://git.cafe/pingdotgg/t3code/pull/23869/diffs?after=ImFwcHMvd2ViL3NyYy9pbmRleC5jc3Mi&expectedVersion=29&format=markdown)
