Open Claude UI
中文
v1 · Claude UI Design Handbook (1.x)

Claude Desktop UI: Architecture & Collaboration Topology

Claude Desktop architecture: Electron window and vibrancy, sidebar drag-and-drop with a 4px dead zone, Cowork board, scroll pinning, roving focus.

中文版Last updated:

⬅️ Previous: Chapter 5: Audio, Media & Artifact Interaction Physics · 📚 Back to the 1.x overview · Next: Chapter 7: Engineering Config & Code Asset Overview ➡️


This chapter documents the desktop-specific architecture of Claude Desktop 1.x: the Electron host window and macOS materials, sidebar drag-and-drop, the Cowork multi-agent board, scroll pinning during streaming, container-query layout, keyboard focus, rate-limit handling and the multi-window Broker-Replica model. It is a translation of the Chinese handbook chapter; every value is copied from it unchanged, and code blocks are reproduced as-is.

Chapter 6: Desktop Architecture & Collaboration Topology

Technical domain: Desktop Host Architecture & Multi-Agent Topology Source of the analysis: extracted with REA (Reverse Engineer Anything) v6.5.0 from the macOS production build /Applications/Claude.app (@ant/desktop v2.37337.1, build timestamp 2026-10-13T22:16:06Z), with an in-depth look at the Electron host app.asar and index.chunk-CiiI592G.js, the style asset c6a992d55-sXiDGMFQ.css, the sidebar reordering topology shared-21-CGGmSsMS.js / shared-22-DYCF-SVs.js, the multi-agent engine shared-6-zKKz0bS2.js, the viewport pinning algorithm ca2ef848d-Dz47ULRM.js and the 429 rate-limit recovery state machine shared-50-Bmc9oUMm.js.


6.1 Deep macOS host integration and window materials

Technical domain: macOS Desktop Integration & Vibrancy

On desktop, Claude does not simply load a web page into a single window. It uses a decoupled two-layer Host BrowserWindow + Transparent WebContentsView setup.

6.1.1 Core production code evidence (app.asar / mainWindow.js / index.chunk-CiiI592G.js)

// index.chunk-CiiI592G.js: 主窗口与透明内容视图构建
var TRAFFIC_LIGHT_DIAMETER = 12; // fBn: macOS 红绿灯按钮直径 12px
var TRAFFIC_LIGHT_BASELINE = 16.5; // gBn: 33 / 2, 标准 45px 顶栏物理中心线

// 核心红绿灯逆缩放几何补偿公式
function getTrafficLightPosition(zoomFactor) {
  var radius = TRAFFIC_LIGHT_DIAMETER / 2; // 6px 半径
  var calc = (pos) => Math.max(0, Math.round((pos + radius) * zoomFactor - radius));
  return {
    x: calc(TRAFFIC_LIGHT_BASELINE),
    y: calc(TRAFFIC_LIGHT_BASELINE)
  };
}

// 1. 实例化主宿主窗口
var hostWindow = new BrowserWindow({
  titleBarStyle: "hidden",
  titleBarOverlay: false,
  trafficLightPosition: getTrafficLightPosition(1),
  // 原生主题即时着色,彻底消除白闪
  backgroundColor: nativeTheme.shouldUseDarkColors ? "#151515" : "#fcfcfb",
  show: false,
  webPreferences: { preload: path.join(__dirname, "mainWindow.js") }
});

// 2. 实例化子视图 WebContentsView 并彻底透明化
var contentView = new WebContentsView({
  webPreferences: { preload: path.join(__dirname, "mainView.js"), sandbox: true }
});
contentView.setBackgroundColor("#00000000"); // 100% 透明穿透
hostWindow.contentView.addChildView(contentView);

// 3. 动态边缘圆角裁切算法
var updateRadius = () => {
  // 窗口化赋予 8px 圆角,最大化或全屏时收缩为 0px,防止黑色锯齿边缘溢出
  contentView.setBorderRadius(hostWindow.isMaximized() || hostWindow.isFullScreen() ? 0 : 8);
};
hostWindow.on("maximize", updateRadius);
hostWindow.on("unmaximize", updateRadius);
hostWindow.on("enter-full-screen", updateRadius);
hostWindow.on("leave-full-screen", updateRadius);
// shared-19-DmuztgN-.js: Web 端安全区动态计算
var TRAFFIC_LIGHT_SPACER_WIDTH = 74; // ew = 74px: 红绿灯物理占位宽

function computeTitlebarInsets(isMac, isFullscreen, isDesktop, incognito) {
  // 全屏状态下 macOS 自动隐藏交通灯,inset 动态归零
  var needsPadding = isMac && !isFullscreen;
  var inset = needsPadding ? 45 : isDesktop ? (incognito ? 45 : 36) : 0;
  var spacerWidth = needsPadding ? Math.round(TRAFFIC_LIGHT_SPACER_WIDTH / Math.min(zoomFactor, 1)) : 0;
  
  return { inset, spacerWidth };
}
/* 纯 CSS 桌面端环境探测器 (c6a992d55-sXiDGMFQ.css) */
@supports ((-webkit-mask-clip: text)) and (-webkit-app-region: drag) {
  /* 仅在 Chromium / Electron 桌面宿主中生效 */
  .cds-desktop-drag-strip {
    height: var(--desktop-titlebar-inset, 45px);
    -webkit-app-region: drag;
  }
}

/* 全局交互防御拦截类 */
.draggable-none {
  -webkit-app-region: no-drag !important;
  app-region: no-drag !important;
}

Production main window implementation: code-examples/createClaudeMainWindow.ts.


6.2 Sidebar conversation drag-and-drop reordering

Technical domain: Sidebar Drag-and-Drop & Pinning Physics

The Claude Desktop sidebar uses a highly modular, multi-level conversation organization system (source evidence from shared-21-CGGmSsMS.js and shared-22-DYCF-SVs.js), with pinned conversations, drag-to-reorder and grouping across sections.

6.2.1 Core production algorithm evidence (shared-21-CGGmSsMS.js, shared-22-DYCF-SVs.js)

In shared-21-CGGmSsMS.js, conversation items are wrapped as pinReorder entities with a reorder interface:

function mV(e, t) {
  return [
    ...fV(e.canPin && e.pinReorder?.onMoveUp, NB),
    ...fV(e.canPin && e.pinReorder?.onMoveDown, PB),
    ...fV(e.canPin, FB),
  ];
}

In shared-22-DYCF-SVs.js, the sidebar state machine declares the full set of persisted fields and drag flags:

var rb = [
  "pinnedOrder",
  "homeProjectsPinnedOrder",
  "groupByByMode",
  "sortByByMode",
  "recentsTypeFilter",
  "recentsStatusFilter",
  "coworkActivityDays",
  "routinesSidebarPlacement",
  "navPinnedIds",
  "navPinV"
];

// 拖拽激活时的 DOM 标记注入
a.toggleAttribute("data-session-dragging", i);
a.toggleAttribute("data-pin-dragging", zt);

6.2.2 Optimistic reorder state machine and persistence (pinnedOrder: string[])

Sidebar order does not simply depend on the backend's update timestamps; it is kept as an ordered ID array, pinnedOrder: string[].

  • Optimistic Mutation: when the user finishes a drag-and-drop, the front end immediately cuts and inserts the item in the local state array (result.splice(targetIndex, 0, movedItem)), for 0ms visual response;
  • Debounced Sync: persistence middleware batches the latest pinnedOrder to the local Electron config store and the cloud.

6.2.3 Pointer dynamics and the 4px dead-zone filter (Pointer Drag Threshold Physics)

To keep a click to select a conversation, a long press for the context menu, or a quick swipe from starting a drag by accident:

  • 4px Dead-Zone Threshold: onPointerDown records the starting point $(x_0, y_0)$, and only when the movement satisfies: $$\Delta d = \max(|x - x_0|, |y - y_0|) > 4\text{px}$$ is isDragging set to true, entering the drag state and showing the handle.

6.2.4 Ghost Elevation & Drop Indicators

  • Ghost Preview: the dragged element tilts slightly by -1.5°, its opacity drops to 40%, and its outline becomes a border-dashed clay border;
  • Midpoint Collision: while dragging over a target row, the pointer's vertical position within the row's rectangle decides: $$\text{Position} = \begin{cases} \text{'before'}, & y_{\text{cursor}} < y_{\text{top}} + \frac{h}{2} \ \text{'after'}, & \text{otherwise} \end{cases}$$ A 2px elastic highlight rail in brand clay (var(--cds-fill-brand)) is drawn live above or below the target.

Production component: code-examples/DraggableSessionList.tsx.


6.3 Multi-agent board and the Cowork space

Technical domain: Cowork Space & Agent Delegation Topology

The latest Claude Desktop / Code clients let teams or automated pipelines organize several agents as a "Cowork space" (source evidence from shared-6-zKKz0bS2.js and shared-21-CGGmSsMS.js).

6.3.1 Core production algorithm evidence (shared-6-zKKz0bS2.js, shared-21-CGGmSsMS.js)

The multi-agent conversation channel and delegation flags extracted from shared-6-zKKz0bS2.js:

// 协同会话类型与通道委派动作
case "channel_delegation":
case "channel_member_session":
case "channel_remote_control_session":
  return renderCoworkSessionCard(e);

6.3.2 Multi-agent delegation state machine and the shared artifact pool

  • Delegation lifecycle: tasks go from the Lead Architect to domain-specific sub-agents (for example an analysis expert, a UI engineer, a QA reviewer), and the state machine is: idle → running (clay wave pulse) → waiting_review (awaiting human confirmation) → completed;
  • Shared Artifacts Pool: each agent's output (documents, architecture diagrams, component code, audio scripts) syncs automatically to the project's shared artifact pool, whose cards support type filters and one-click preview comparison.

6.3.3 Cross-window event sync over BroadcastChannel

Agents may run in different windows or tabs, so the system broadcasts state heartbeats and task-completion signals over the browser's native BroadcastChannel("claude_cowork_sync"), keeping every panel in sync without delay.

Production component: code-examples/CoworkAgentCanvas.tsx.


6.4 Streaming long output and scroll viewport physics

Technical domain: Streaming Token Pipeline & Scroll Physics

When long text or code streams out, the biggest experience problem is "scroll jitter and viewport hijacking": if the user is scrolling up through earlier messages and the viewport is yanked to the bottom with every token, it feels like fighting the app.

6.4.1 Core production algorithm evidence (ca2ef848d-Dz47ULRM.js)

In ca2ef848d-Dz47ULRM.js, Anthropic combines a 150ms cooldown timestamp + user wheel interruption + a 35vh bottom cushion + a 6px hardware-accelerated blur dissolve:

// ca2ef848d-Dz47ULRM.js: 生产环境滚动图钉判定算法
var COOL_DOWN_MS = 150;
var lastUserInputAt = 0;
var programmaticScroll = false;
var isPinned = true;

// 1. 监听用户物理输入:wheel / touchmove / keydown / mousedown
function markUserInput() {
  lastUserInputAt = performance.now();
  isPinned = false; // 瞬间释放图钉锁定,绝不与用户手势争夺视口
}

// 2. Token 追加时的视口图钉判定算法
function onTokenChunkAppended(containerEl) {
  // 若用户在 150ms 内触发过物理输入,严格禁止任何程序化滚动调整
  if (performance.now() - lastUserInputAt <= COOL_DOWN_MS) {
    return;
  }

  // 判定视口当前是否处于底部边缘(容差 40px)
  var distanceFromBottom = containerEl.scrollHeight - containerEl.scrollTop - containerEl.clientHeight;
  if (distanceFromBottom <= 40) {
    isPinned = true;
  }

  // 若处于锁定态,以非平滑(behavior: auto)即时对齐底部,防止 requestAnimationFrame 帧率滞后抖动
  if (isPinned) {
    programmaticScroll = true;
    containerEl.scrollTo({
      top: containerEl.scrollHeight,
      behavior: "auto"
    });
  }
}
/* 35vh 底部视口缓冲垫与字符硬件消解动画 */
.chat-scroll-container {
  /* 强制在最新一条消息下方预留 35% 屏幕高度的弹性缓冲,使阅读焦点永远在黄金分割线 */
  --chat-extra-space: 35vh;
  padding-bottom: var(--chat-extra-space);
}

/* 新 Token 到达时的 6px GPU 模糊消解动画(c6a992d55-sXiDGMFQ.css) */
@keyframes _grapheme-reveal_1plog_1 {
  0% {
    opacity: 0.2;
    filter: blur(6px);
    transform: translateY(4px);
  }
  100% {
    opacity: 1;
    filter: blur(0px);
    transform: translateY(0);
  }
}
.token-reveal {
  animation: _grapheme-reveal_1plog_1 120ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
  will-change: filter, opacity, transform;
}

Production Hook: code-examples/useClaudeScrollPinning.ts.


6.5 Container-query adaptive layout and squeeze protection

Technical domain: Container Queries & Anti-Squash Grid Guidelines

In modern, complex web/desktop clients (such as Claude Desktop), you must not rely blindly on viewport media queries (Window Media Queries, such as @media (min-width: 768px)).

6.5.1 Why window media queries distort the UI

At desktop resolutions of 1440px / 1920px, the viewport is always in the wide range. If a component sits in a fixed 288px sidebar, a 390px drawer (Sheet) or a floating popover card:

  • the viewport media query @media (min-width: 768px) wrongly decides it is a wide screen and forces grid-cols-4;
  • cards that need room are squeezed into columns only 70px wide;
  • Chinese text can't form lines and breaks into one character per line, and the layout collapses completely!

6.5.2 Core production code evidence (c6a992d55-sXiDGMFQ.css)

The full set of named containers and the 4-step fluid compression breakpoints extracted from c6a992d55-sXiDGMFQ.css:

/* 1. 容器声明:为主消息流与操作栏建立独立容器上下文 */
.chat-feed-container {
  container-type: inline-size;
  container-name: feed;
}
.chat-toolbar-container {
  container-type: inline-size;
  container-name: toolbar;
}

/* 2. 4 阶弹性流体压缩断点梯队 */

/* Tier 1: >= 576px (豪华完整态) */
@container (min-width: 576px) {
  .model-selector-label { display: inline-block; }
  .action-button-text { display: inline; }
  .keyboard-shortcut-badge { display: inline-flex; }
}

/* Tier 2: < 480px (紧凑折叠态) */
@container (max-width: 480px) {
  .keyboard-shortcut-badge { display: none !important; }
  .toolbar-auxiliary-actions { display: none; }
}

/* Tier 3: < 384px (极简图标态) */
@container (max-width: 384px) {
  .action-button-text { display: none; }
  .model-selector-label { max-width: 4rem; overflow: hidden; text-overflow: ellipsis; }
  .chat-composer-buttons { gap: 4px; }
}

/* Tier 4: < 320px (零间距防爆安全网) */
@container (max-width: 320px) {
  .chat-composer-inner {
    padding-left: 4px !important;
    padding-right: 4px !important;
  }
  /* 采用 cqw 容器查询宽度安全封顶,杜绝水平滚动条溢出 */
  .chat-composer-prompt {
    max-width: max(6rem, calc(100cqw - 4rem)) !important;
  }
}

6.5.3 The go-to combination against broken CJK text

On card titles, badges and button text, always combine:

/* 杜绝中文单字纵向断裂走样 */
.cds-card-title {
  text-wrap: balance;          /* 均衡行长 */
  overflow-wrap: break-word;   /* 正常单词换行 */
  word-break: normal;          /* 严禁无理打碎词汇 */
  min-width: 0;                /* 允许 flex 容器正确收缩 */
}

6.6 Full keyboard navigation grid and accessibility

Technical domain: Keyboard Focus Roving & A11y Grid

For heavy keyboard users, Claude builds a roving focus grid based on Roving TabIndex and full accessibility paths (evidence from c16e267d0-B2-4AuUD.js):

// c16e267d0-B2-4AuUD.js: 漫游网格键位映射与 RTL 镜像翻转
var KEY_INTENT_MAP = {
  ArrowLeft:  "prev",
  ArrowUp:    "prev",
  ArrowRight: "next",
  ArrowDown:  "next",
  PageUp:     "first",
  Home:       "first",
  PageDown:   "last",
  End:        "last"
};

function resolveKeyDirection(key, dir) {
  // 阿拉伯语/希伯来语等 RTL 场景下自动镜像
  return dir === "rtl"
    ? key === "ArrowLeft" ? "ArrowRight" : key === "ArrowRight" ? "ArrowLeft" : key
    : key;
}

// 漫游焦点键盘事件驱动核心
function onRovingItemKeyDown(event, groupState) {
  // 核心规则 1:Tab / Shift+Tab 触发时必须立即释放焦点流,防止形成无障碍键盘陷阱
  if (event.key === "Tab") {
    groupState.onItemShiftTab();
    return;
  }
  if (event.metaKey || event.ctrlKey || event.altKey) return;

  var mappedIntent = KEY_INTENT_MAP[resolveKeyDirection(event.key, groupState.dir)];
  if (!mappedIntent) return;

  event.preventDefault();
  var focusableItems = groupState.getItems().filter(item => item.focusable);
  var currentIndex = focusableItems.indexOf(event.currentTarget);

  var nextIndex = currentIndex;
  if (mappedIntent === "first") nextIndex = 0;
  else if (mappedIntent === "last") nextIndex = focusableItems.length - 1;
  else if (mappedIntent === "prev") nextIndex = currentIndex - 1;
  else if (mappedIntent === "next") nextIndex = currentIndex + 1;

  // 核心规则 2:loop 边界回绕处理
  if (groupState.loop) {
    nextIndex = (nextIndex + focusableItems.length) % focusableItems.length;
  } else {
    nextIndex = Math.max(0, Math.min(focusableItems.length - 1, nextIndex));
  }

  // 核心规则 3:仅选中的目标项维持 tabIndex = 0,其余兄弟节点全置为 -1
  groupState.updateCurrentTabStop(focusableItems[nextIndex].id);
  focusableItems[nextIndex].focus({ preventScroll: true });
}

Production Hook: code-examples/useClaudeRovingFocus.ts.


6.7 Rate limiting, failure handling and business state machines

Technical domain: Error Resilience & UI State Machines

To cope with very high concurrency and unreliable networks, Claude builds multi-level defenses and self-healing (source evidence from shared-50-Bmc9oUMm.js and cd5a31703-B4nKQMBN.js).

6.7.1 Core production algorithm evidence (shared-50-Bmc9oUMm.js)

// shared-50-Bmc9oUMm.js: 429/503 指数退避与常数矩阵
var MIN_RETRY_DELAY = 5000;   // OR = 5000ms: 基准重试下限 5s
var MAX_RETRY_DELAY = 600000; // DR = 600000ms: 封顶重试上限 10min

// 指数退避算法公式:base * 2^(attempts - 1)
function calculateExponentialBackoff(baseDelay, attemptCount) {
  return Math.max(baseDelay, MIN_RETRY_DELAY) * Math.pow(2, attemptCount - 1);
}

function createErrorResilienceMachine(errorEvent, currentAttempts = 0) {
  var isOverloadOrLimit = errorEvent.status === 429 || (errorEvent.status === 503 && errorEvent.type === "overloaded_error");
  var nextAttempt = isOverloadOrLimit ? currentAttempts + 1 : 0;

  // 核心规则:最多自动重试 3 次 (currentAttempts < 3)
  // 超过 3 次强制切换为手动点击重试,防止雪崩放大效应
  var backoffDelay = (errorEvent.retryAfterMs !== void 0 && nextAttempt > 0 && nextAttempt < 3)
    ? Math.min(calculateExponentialBackoff(errorEvent.retryAfterMs, nextAttempt), MAX_RETRY_DELAY)
    : void 0;

  return {
    waitsInARow: nextAttempt,
    canAutoRetry: backoffDelay !== void 0,
    retryAtMs: backoffDelay !== void 0 ? Date.now() + backoffDelay : null
  };
}
// cd5a31703-B4nKQMBN.js: Thinking 思考过程折叠互斥锁算法
var ThinkingAccordion = function({ text, expansionKey }) {
  var isThinkingLive = useModelThinkingStreamState(); // 模型是否正处于思考流式输出阶段
  var [userExpanded, setUserExpanded] = usePersistentToggle(expansionKey);

  return (
    <AccordionStep
      label="Thought process"
      // 核心互斥物理:生成中必定强制展开,忽略用户收起偏好
      open={isThinkingLive || (userExpanded ?? true)}
      // 核心权限剥夺:生成中传递 undefined,彻底禁用折叠手势,防止视口抖动
      onOpenChange={isThinkingLive ? undefined : setUserExpanded}
    >
      {text}
    </AccordionStep>
  );
};

Production Hook: code-examples/useRateLimitBackoff.ts.


6.8 Broker-Replica multi-window rendering and state sync topology

Technical domain: Broker-Replica Window Architecture & Multi-Window IPC Topology

In Claude Desktop 2.0, when the user clicks "Open in New Window" on an Artifact on the right or on a separate conversation, the system does not just start an isolated Electron renderer window. It builds a precise primary/replica topology (source evidence from frame-shell-broker-znyoRmsX.js and frame-shell-replica-C7KCVFfZ.js).

flowchart TD
    subgraph MainProcess ["Electron 主进程 (Main Process)"]
        WM["窗口管理器 (WindowManager)"]
    end

    subgraph BrokerWin ["Broker Window (主代理渲染窗口)"]
        WS["权威 WebSocket 连接池"]
        TokenTracker["Prompt Caching & Token 计量器"]
        MountHost["工作区文件读写宿主 (LocalDocumentHost)"]
        Writer["ReplicaWriter (状态分发与快照复制器)"]
        WS --> Writer
        TokenTracker --> Writer
        MountHost --> Writer
    end

    subgraph ReplicaWin1 ["Replica Window A (独立工件预览窗口)"]
        ViewA["轻量渲染视口 (Artifact View)"]
        ReceiverA["IPC 消息信道 (MessagePort)"]
        ReceiverA --> ViewA
    end

    subgraph ReplicaWin2 ["Replica Window B (多任务会话分身)"]
        ViewB["会话交互视口 (Session View)"]
        ReceiverB["IPC 消息信道 (MessagePort)"]
        ReceiverB --> ViewB
    end

    MainProcess --> BrokerWin
    MainProcess --> ReplicaWin1
    MainProcess --> ReplicaWin2
    Writer <===>|"双向安全信道 (Wireable Entry)"| ReceiverA
    Writer <===>|"双向安全信道 (Wireable Entry)"| ReceiverB

6.8.1 A single authoritative broker and the ReplicaWriter snapshot stream

To avoid redundant connections, double quota billing and split-brain races on local file reads/writes when several windows run at once:

  1. One authoritative long-lived connection: however many extra windows are open, only the Broker Window keeps the single full-duplex WebSocket connection to Anthropic's edge gateway;
  2. The ReplicaWriter distribution protocol: the primeReplicaWriter(target, channel) protocol extracted from frame-shell-broker-znyoRmsX.js:
    • the primary window streams computed results, the live render AST and ETag version signatures to each Replica window through a lock-free binary pipe;
    • user interactions inside a Replica window (checkboxes, form submits, code edits) are proxied back to the Broker, which schedules and executes them.

6.8.2 Workspace mount conflict arbitration (baseline-mismatch)

When the user views a code artifact in one window while Claude keeps editing the file in another:

  • the system runs an against_sha256 baseline check through RemoteDocumentHost;
  • if the file was changed concurrently from outside or by another window, it immediately raises a baseline-mismatch state and asks the user to review the diff, so code is never overwritten silently.

⬅️ Chapter 5: Audio, Media & Artifact Interaction Physics · 📚 1.x overview · Chapter 7: Engineering Config & Code Asset Overview ➡️ · Live demos: desktop architecture & hooks

Open Claude UI is an unofficial community analysis, MIT licensed. Claude is a trademark of Anthropic; this project is not affiliated with Anthropic.

On this page