This release adds 2 notable features for engineering teams evaluating rollout.
✓ No known CVEs patched in this version
Topics
+4 more
Summary
AI summaryFlow board gains live Codex session state badges and first‑class edge/flow node manipulation.
Changes in this release
| Type | Severity | Summary | CVE |
|---|---|---|---|
| Feature | Low |
Adds a quick‑close × button in the status rail that collapses and persists across reloads. Adds a quick‑close × button in the status rail that collapses and persists across reloads. Source: llm_adapter@2026-06-07 Confidence: high |
— |
| Feature | Low |
Introduces a live rate knob for text‑to‑speech playback, tunable between 0.5× and 2.5×. Introduces a live rate knob for text‑to‑speech playback, tunable between 0.5× and 2.5×. Source: llm_adapter@2026-06-07 Confidence: high |
— |
| Feature | Low |
Renders ```mermaid``` fenced code blocks as SVG diagrams via lazy‑loaded Mermaid v10. Renders ```mermaid``` fenced code blocks as SVG diagrams via lazy‑loaded Mermaid v10. Source: llm_adapter@2026-06-07 Confidence: high |
— |
| Feature | Low |
Makes Flow board background have extra padded pan space for edge items. Makes Flow board background have extra padded pan space for edge items. Source: llm_adapter@2026-06-07 Confidence: high |
— |
| Feature | Low |
Adds Cmd+` (and Cmd+Shift+`) shortcuts to cycle through CCC windows on macOS. Adds Cmd+` (and Cmd+Shift+`) shortcuts to cycle through CCC windows on macOS. Source: llm_adapter@2026-06-07 Confidence: high |
— |
| Feature | Low |
Makes Flow “Organize” incremental, preserving existing placements and reporting total pixel displacement. Makes Flow “Organize” incremental, preserving existing placements and reporting total pixel displacement. Source: llm_adapter@2026-06-07 Confidence: high |
— |
| Bugfix | Medium |
Strips unpaired UTF-16 surrogate code points before Anthropic API calls, preventing 400 errors. Strips unpaired UTF-16 surrogate code points before Anthropic API calls, preventing 400 errors. Source: llm_adapter@2026-06-07 Confidence: high |
— |
| Bugfix | Medium |
Prevents stale "▶ Bash /bin/zsh -c …" in‑flight pill on idle Cursor sessions. Prevents stale "▶ Bash /bin/zsh -c …" in‑flight pill on idle Cursor sessions. Source: llm_adapter@2026-06-07 Confidence: high |
— |
| Bugfix | Medium |
Ensures "Launch in Terminal" uses an existing cwd, avoiding fallback to home directory. Ensures "Launch in Terminal" uses an existing cwd, avoiding fallback to home directory. Source: llm_adapter@2026-06-07 Confidence: high |
— |
| Bugfix | Medium |
Prevents user‑typed messages from disappearing after cleanIssuePrompt over‑stripping. Prevents user‑typed messages from disappearing after cleanIssuePrompt over‑stripping. Source: llm_adapter@2026-06-07 Confidence: high |
— |
Full changelog
Added
-
Codex sessions now show a live state badge (Working / Idle / Stuck / Offline) on the conversation row and in the conversation pane, derived from the rollout log — fixing pool-model codex sessions that previously showed no activity indicator.
-
Flow edges (the curved lines connecting child nodes to their parent object/repo) are now first-class objects you can manipulate:
-
Click an edge to select it. Selected edges thicken and turn orange so they stand out from the rest of the board.
-
Backspace / Delete with an edge selected removes the parent assignment; the child falls back to its default repo group (or no parent). Skipped automatically when focus is in a text field so the shortcut doesn't hijack typing.
-
Drag any edge to reconnect it. Pointer-down on the line starts a reparent drag — a dashed ghost line follows the cursor, candidate parent nodes light up orange, and dropping on one re-links the child to that new parent. Drop outside any node to cancel. Cycle-prevention: a node can't become its own ancestor.
-
Click background or hit Escape to clear the edge selection.
Edges now render as <g class="flow-edge"> carrying a wide invisible hit path on top of the thin visible line — clicking the visible 1.6px stroke is unreasonably hard, so the hit-target widens to 14px while staying invisible.
-
Group chats are now first-class nodes on the Flow workspace alongside repos and objects:
-
Render: every entry in the existing
_gcActiveChatscache shows up as a cyan-accentedflow-node-group-chatcard with the chat's topic, participant count, status, and last-activity timestamp. -
"+ Group chat" toolbar button sits next to "+ Object". Click it and the existing new-group-chat dialog (window-prompt for the name,
/api/coordinatePOST) runs; oncepollGcActiverefreshes, the new node appears on the board automatically. -
Drag a session node onto a group-chat node to add the session as a participant — same outcome as dragging a conv-list row onto a chat row in the sidebar. The session card snaps back to its repo cluster (sessions stay under their repo for layout purposes; the chat just registers the participation via
/api/group-chats/add-participant). -
Click a group-chat node to open the chat reader through the existing
openGroupChatReaderentry point.
All three node kinds (repo / object / group-chat) participate in the Organize layout the same way — they anchor at their current position and the overlap resolver minimises movement.
- Added a button to the Flow popout's toolbar (the small split-rectangle icon) that toggles a conversation reader pane on the right side of the popout window. With the reader on, clicking a node in the flow board mounts that conversation into the right pane through the normal
selectConversationpath — same conv reader, same input bar, same TTS / Esc / Send buttons. With the reader off (the default), the flow board fills the whole popout. The toggle is persisted across popout reloads in localStorage (ccc-flow-popout-reader). No new conv-rendering code — just un-hides the existing main pane and splits the viewport via CSS. - The Flow view now has its own pop-out button in the flow toolbar (next to the Expand toggle). Click it and the whole Flow board opens in its own window — a native CCC window when running inside the macOS app, a browser popup otherwise — reusing the same
window.open+cccNative.openPopout+/api/open-browserfallback chain the conversation pop-out already uses. Boot-time detection of?ccc_popout=flowadds abody.flow-popoutclass, forcesccc-session-view=flowin localStorage so the popped-out tab lands on the board immediately, sets the window title to "Flow", and CSS hides every other surface (main pane, topbar, attention panel, conv list, kanban) so the flow board fills the viewport. The button is hidden inside the popout itself (no point popping a popout). - Flow popout's conv reader (right pane) is now draggable: a 6px column between the flow board and the reader can be dragged left to widen the reader or right to narrow it. Width persists across reloads in
ccc-flow-reader-width. Bounds: min 280px, max viewport width − 320px so the flow board stays usable. The CSS custom prop--flow-reader-widthdrives the .main flex-basis so the change is instant and animation-free. - Flow adds Record mode and Organize+ for replaying recorded manual layout preferences.
- Flow repo/object nodes now open editable Markdown-backed work-item status pages in the conversation pane, with refreshable auto sections and deterministic per-work-item accent colors on the board.
- New "Group chats" modal lists every group chat with a per-row pause / unpause button. Opens from the small ⚙ button next to "+ New Group chat" in the sidebar. Each row shows the topic, current status (active / paused / closed) in colour, the participant count, and time since last activity. Pausing or resuming routes through the existing
setGroupChatPausedAPI (and benefits from the optimistic local update so the row's status flips immediately). Sort: newest activity first. Esc or backdrop click closes the modal. - Cmd+
cycles through CCC's open windows (main ↔ flow popout ↔ conversation popout), and Cmd+Shift+cycles in reverse. Both surface as explicit "Cycle Through Windows" / "Cycle Through Windows (Reverse)" items in the Window menu, so the keystroke is bound at the menu-bar level — macOS' default Cmd+` works for AppKit apps with multiple windows, but WKWebView often swallows the keystroke before AppKit sees it, which is why pop-outs felt like dead ends. DMG users get this only via a Sparkle release (scripts/macapp/ change). mermaid ``` fenced code blocks in assistant messages now render as actual SVG diagrams instead of showing rawflowchart TD …source.renderCodeBlockemits a.mermaid-blockcarrier whose.mermaid-sourcepre is the offline fallback; a lazy loader fetchesmermaid@10fromcdn.jsdelivr.neton first appearance and converts every pending block into an SVG. Hooked into the existing conv-view MutationObserver (the same one that tags blocks for RTL), so every render path — assistant text, stream bubbles, group-chat messages, issue bodies — picks up the rendering for free. Loader is cached after first call; if the CDN is unreachable, the fallback source pre stays visible with adata-mermaid-error="load-failed"marker. Diagram theme follows the dashboard theme (darkby default,defaultwhen[data-theme=light]).- The status rail (right-side panel with Original ask / Activity / Files) now has a quick-close × button in its top-right corner. Click it and the rail collapses immediately and stays collapsed across reloads (writes
ccc-status-rail-collapsed=1, same persistence as the existing topbar toggle). Previously the only way to close the rail was to drag the resizer to the edge or find the topbar toggle button — neither obvious. The × only shows when the rail is open in right-position mode, so it doesn't appear when the rail is already collapsed (the topbar restore button handles the un-collapse). - Text-to-speech playback now has a live rate knob next to the TTS button — a thin range slider that appears while playback is active or paused, defaulting to 1.25× and tunable between 0.5× and 2.5×. Dragging it cancels the in-flight utterance and re-speaks from the most recent word boundary so the new rate kicks in within ~180ms (debounced so per-pixel drags don't stutter), instead of having to wait for the next message to hear the change. The rate is persisted to localStorage (
ccc-tts-rate) so it sticks across sessions — set it to 1.2× once and it stays there.
Changed
- Conversation row's live-tool pill now shows just the tool label (e.g. "Reading file" — glowing when in-flight) instead of "Reading file ...s/claude-command-center/static/app.js". The path detail was ellipsizing into unreadable suffixes and pushing the rest of the row meta (size, branch, age, action buttons) off-screen on narrower sidebars. The full file/command still appears in the hover title, so users who want to confirm exactly what's being touched can read it there.
- Shifted Cursor IDE integration from a planned full two-way chat sync to a metadata bookmark sync. Cursor's Desktop IDE compiles chat UI state into an undocumented Protobuf Merkle tree in
store.dbrather than simple JSON strings. Injecting full chat history natively carries a severe risk of corrupting user workspaces when Cursor pushes minor internal updates. CCC now safely injects only the session metadata intostore.dbso you can see your CLI sessions listed in the IDE sidebar, but the full interactive chat history remains safely decoupled in the CCC dashboard. - Flow board background now has extra padded pan space around every edge so top-left items can be dragged toward the center of the viewport.
- The Flow toggle button (☷ icon in the sidebar header) now pops the Flow board into its own window instead of swapping the sidebar contents in-place. Reuses the existing
openFlowPopouthelper — native CCC window inside the macOS app, browser popup otherwise. When clicked from INSIDE the flow popout itself, falls back to the legacy in-sidebar swap (no point popping a popout). The "+ New session" / "+ New Group chat" panel and the conv list stay visible in the main window so the user can keep working without flipping sidebar modes. - Flow "Organize" is now incremental — it keeps repos and objects exactly where you put them and only moves them when it absolutely has to. Per user request: "move repos and objects as least as possible. The only case we're OK moving them is if we cannot form a rectangle that includes the sessions beneath them and the object."
Previously every run bin-packed every chain from the top-left, which scrambled a board the moment you ran it. Now each chain anchors at its root's current position; if two chain bounding boxes overlap, a greedy resolver picks the worst-overlapping pair, pushes the chain that has moved less so far by the minimum right/down amount, and repeats until clean. Re-running Organize on an already-tidy board is a no-op (zero pixels moved). The toast at the end reports the total pixel displacement so you can see how much it had to nudge.
Untouched chains (first-ever Organize, root still at 0,0) seed from the legacy bin-pack cursor so a fresh board still produces a tidy initial layout. The minimum-displacement rule is now R10 in the in-source algorithm doc block.
Fixed
- "Active Group chat" pill no longer lingers after the user stops orchestration. Two fixes in
gcShouldShowActivePill:
-
Hard short-circuit on paused / closed / orchestrator-off. A chat with
status === 'paused',paused === true, ororchestrator_timer_active === falsereturns false from the show-gate immediately — the pill claims "active right now"; the moment the user clicks Stop, the pill must respect that, not coast on the trigger-freshness window. -
Dropped
last_mtimefrom the freshness calc. It's the chat file's stat mtime which the server bumps on metadata writes (name_map updates, polled sidecar writes), not real message arrivals. The label-side code already filtered it out for the same reason; the show/hide gate now matches.
Plus an optimistic local patch in setGroupChatPaused so the pill drops within one render tick of the Stop click instead of waiting for the next 15s gcActive poll to land.
- Annotations and any text routed through
_inject_text_into_sessionare now stripped of unpaired UTF-16 surrogate code points (U+D800..U+DFFF) before they can reach an Anthropic API call. Symptom: when a pasted annotation or selected DOM text carried a lone surrogate (the browser's clipboard / selection APIs can split a surrogate pair, especially when a selection ends mid-emoji), the downstream Claude session POSTed a request body containing that surrogate and Anthropic rejected it withAPI Error: 400 The request body is not valid JSON: no low surrogate in string: line 1 column N (char N)— same root asanthropics/claude-code#16294. Fix: new_strip_lone_surrogateshelper at the server boundary, called from_annotation_text(covers/api/annotationsandenqueue_annotation_ux_fixes_queuepayloads) and from_inject_text_into_session(covers every other inject path as belt-and-suspenders). Paired surrogates — real astral-plane characters like 😀 (U+1F600), which Python stores as a single code point — pass through unchanged; only LONE surrogates are dropped. - Fixed Codex slash commands so CCC offers the Codex command catalog and routes
/...commands through a live Codex terminal instead of sending them as headless prompts. - Context percentage compact now uses a dedicated compact API instead of injecting
/compactas ordinary text. Live Claude terminals receive the slash command directly; busy terminals queue it; dormant Claude sessions open an interactiveclaude --resumeterminal and run/compactthere, avoiding the broken headless-resume fallback. - Cursor backfill now correctly sets the
lastUpdatedAtfield to match the most recent transcript activity, ensuring fresh sessions appear at the top of the Cursor IDE history. - Cursor sessions that have gone idle no longer show a stale "▶ Bash /bin/zsh -c …" in-flight pill on their sidebar row. Root: cursor JSONLs don't carry per-event timestamps, so
pending_tool_tsfalls back to the JSONL file's mtime, which keeps refreshing as the file appends metadata-only lines. The codex stale-tool check comparesnow - pending_tool_tsagainst a 15-minute threshold and never tripped because the ts kept looking fresh. A finished cursor turn whose last event was a tool_use would therefore display the in-flight pill indefinitely.
Fix: _cursor_activity_fields_from_tail now checks file idleness directly — if the JSONL hasn't been written to in the configured window (default 60s, env CCC_CURSOR_IDLE_SEC), it returns empty activity fields regardless of the dangling pending_tool. The pill drops as soon as the session stops emitting events.
- Flow button opens or focuses the Flow popout without embedding Flow in the main sidebar.
- When creating a new flow object via the "+ Object" toolbar button, the new node now lands where the input modal was instead of stacking into a fixed top-left grid. The modal's center is captured before awaiting the user's OK (capturing it after would read a zeroed rect since the modal cleans up to
display:nonefirst), then translated into flow-canvas coordinates accounting for the currentflowZoomand the canvas's bounding rect. The node is centered on that point. Window-prompt fallback still uses the old grid layout when the modal element isn't available. - Flow "Organize" no longer lets one repo cluster overlay a child object that sits nested under the previous repo. Root: the placement loop advanced the row cursor by the top-level cluster's own width only — nested clusters were placed to the right of their ancestor but didn't extend the row's right edge, so the next top-level cluster slid right over them. Refactor: group clusters into chains (top-level root + every cluster transitively nested under it), simulate each chain at origin to learn its combined bounding box, then bin-pack chains as single units. The row-budget wrap check now sees the full chain width, so wide chains wrap to a new row instead of bleeding under the next one.
- Two flow-board fixes:
-
Background pan no longer hitches every 90s. The
archiveTimespoller fired itsrefreshArchiveDatafetch unconditionally; even though the resultingrenderArchiveListcorrectly deferred itself when a sidebar drag was in progress, the queued render kicked in right after the drag ended and could clip the pan. Wrap the poller body withdeferSidebarRenderIfDragging()so the whole tick skips while panning — the flush-after-drag hook still replays the deferred render the moment the user releases. -
Nested objects / repos now stack BELOW their parent, not to the right when first added. The unplaced-nested seed used to default to
ancestor.right + NESTED_GAP_Xwhich placed a child repo next to its parent object; the user wanted the layout to match the "Small Projects → video-claw / usage_on_mac" stack-below shape. Seed is now(ancestor.x, ancestor.y + ancestor.h + CLUSTER_MARGIN). Multiple unplaced siblings start at the same slot and the overlap resolver stacks them further down.
- Releasing a flow-board pan no longer snaps the view back to where it started before the drag. Root:
renderFlowSidebarrewrites$flow.innerHTML, which wipes the element'sscrollLeft/scrollTop. While the user was actively panning, the in-progress drag suppressed renders correctly; the moment they released, the deferred-after-drag flush ranrenderFlowSidebarand the innerHTML write reset the scroll. Fix: capture the previous scroll position before the rewrite and restore it immediately afterapplyFlowZoomsettles the canvas size. The pan now sticks where the user released. - Flow board pan (click-and-drag the background) no longer jumps every few seconds while you hold the drag. Root:
isSidebarDragInProgressself-heals by checking the DOM for a.dragging-class element — if the boolean flag is true but no node carries that class, it clears the flag (defense against stuck-true after a cancelled drag). The flow pan callsbeginSidebarDragbut doesn't add.draggingto any node (it's dragging the BACKGROUND, not a node), so the self-heal cleared the flag within one render tick. Once cleared, the next periodicliveStatusorliveSessionsActivitytick passed through_scheduleSidebarRender→renderSidebarunimpeded, swapped the flow board DOM mid-pan, and the user saw nodes jump. Fix: the pan handler now sets.is-panningon the flow board element while the drag is active, and the self-heal selector includes.flow-board.is-panning— so the flag stays honestly true for the full pan duration and pollers' renders correctly defer until release. - The "+ New session / + New Group chat" panel no longer shows up in the Flow pop-out window. It's an entry point into the main dashboard's session-creation flow and has no business in a dedicated flow board view. Added
.new-session-panelto thebody.flow-popouthide list alongside the conv list, search bar, topbar, etc. - Flow wheel/trackpad zoom now defers sidebar refresh renders while the zoom gesture is active, matching the existing pan guard so periodic refreshes cannot interrupt the gesture or snap the board mid-zoom.
- "Launch in Terminal" no longer hallucinates a deleted-worktree cwd and drops the user in their home dir. Two-layer fix:
-
Server (
find_codex_conversations) —effective_cwdused to betail_worktree_path or cwd, surfacing whatever path the JSONL tail extracted from an oldcd <…>Bash command. If that worktree was since deleted, the row carried a non-existent path. Now picks the first cwd candidate that still exists on disk via the new_first_existing_dirhelper (tail → cwd → pinned), falling back to the literal worktree path only when nothing exists. -
Client (
buildResumeCommand) — for missing cwds that don't match the.claude/worktrees/<branch>recreation pattern (e.g. ad-hocBYM-Finie-push-reschedule-sGH1nB),cd '/...' && resumewould fail (no such dir) and the&&would block the resume. Now falls back tocurrentSession.repoPathwhen known; drops thecdentirely (runs resume from the user's terminal pwd) when no repo path is available.
- Mobile single-column layout (conv list full-width → tap a row → conv pane slides in → back button returns to list) now triggers on every phone, including iPhone landscape. Root: the breakpoint was 768px (JS
_mobileMQ) / 720px (back button CSS) / 768px (main-overlay CSS). iPhone Pro Max landscape is 932px and even baseline iPhone landscape is 844px — both exceeded all three thresholds, soisMobile()returned false andmobileShowForCurrentModeno-op'd. The user saw the desktop dual-pane layout cramped onto a phone with no back button. Aligned all three to 950px (covers up to iPhone Pro Max landscape with a small safety margin). The wiring (selectConversation → mobileShowForCurrentMode → mobile-show-main → translateX(0), back button →mobileShowMain(false)) was already in place; this just opens it to the right viewports. - Mobile conversations keep a visible back-to-list button inside the conversation pane.
- Mobile conversations keep one header while preserving the back-to-list button.
- On mobile, the page now lands on the conv list instead of the auto-restored conv pane. Root:
restoreLastConversationruns at boot and callsselectConversationfor whichever conv was open last, which in turn triggersmobileShowForCurrentModeand slides the conv pane over the sidebar. The user therefore landed on a conv overlay every page load and never saw the list — opposite of the standard phone pattern. Fix: when the restore loop completes on a mobile viewport, callmobileShowMain(false)to slide the conv pane back off-screen. The conv stays loaded so tapping a row brings it back instantly with no fetch latency. - Three mobile toolbar fixes in one ship:
-
Reverted the temporary blue Annotate button — it had served its purpose as a cache-bust probe and the user confirmed new CSS reaches the browser.
-
Topbar now fits one row on phones instead of wrapping into a second row that pushed the back button off-screen. Hidden at
max-width: 950px: Update pill, Report-a-bug, Annotate / Screen / Notes, Worktrees, Stats, Terminal, Vercel / localhost deploy pills, and the status-rail position toggle. None of these are phone-friendly anyway. The breadcrumb now flexes to fill remaining space; its category chip caps at 96px so the conv title gets visible room. -
Right status rail (Original ask / Activity / Files) defaults to collapsed on mobile. The mobile viewport doesn't have the spare width to host the rail, and surfacing it pinches the conv reader into a narrow column. Boot-time check in
index.htmladdsstatus-rail-collapsedwhen the viewport is ≤950px UNLESS the user has explicitly opened it (localStorage = '0'); the desktop default behavior is unchanged.
- Organize now respects hand-placed nested objects. Previous R10 implementation only anchored the chain ROOT at its current position — every nested cluster was placed at a chain-derived offset (
ancestor.right + NESTED_GAP_X, ancestor.top), so a nested object you'd dragged anywhere different would snap back to "right of ancestor" on every Organize run. New behavior: every cluster (root AND nested) anchors at its own parent's currentoffsetLeft/offsetTop. The overlap resolver runs over all clusters as independent units instead of as chain bounding boxes, so a nested object can stay where you put it while its repo and sibling repos stay where THEY were too. Unplaced clusters still fall back to sensible seeds — bin-pack cursor for top-level, "right of ancestor" for nested — so first-ever Organize still tidies a fresh board. - Inline session rename no longer gets stuck in edit mode when the sidebar search box is open (or focused). Root: the rename input is itself a text input, and after Enter/blur focus is either still on it or has moved to the search box (also text) —
shouldPauseSidebarRenderreturns true for either, so the post-commitrenderSidebarcall early-returned and the rename input was never swapped back for the rendered title. Same class of bug as the "Sending… pill" fix shipped earlier. Fix: the rename commit'srenderSidebarcall now passes{ force: true }to bypass the periodic-pause guard for user-initiated paints. The save still happens (the API call ran), it just wasn't visible. - Sidebar search now hides active and archived group-chat rows so search results stay focused on matching sessions/issues instead of showing group-chat navigation rows in In progress or Archived.
- Command tool results now attach to the matching command and show a visible result/error label.
- User-typed messages no longer disappear from the conv view when
cleanIssuePromptover-strips them. Root: the conv reader runs the JSONL user_text throughcleanIssuePrompt(which removes spawn-prompt boilerplate, session-state instructions, slash-command markup, etc.) before rendering. If the cleanup eats the entire body — possible when a regex matches too broadly or the user's prose happens to look like template plumbing — the user_text div rendered with just a "User" label and no content. Combined with the pending-echo dedupe (which removes the optimistic stub the moment a matching JSONL event arrives), the user saw their sent message silently disappear. Safety net: whencleanIssuePromptreturns empty but the rawev.texthad content, fall back to the raw text. The user's typed words never disappear from their own conv view.
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About CCC
All releases →Related context
Related tools
Earlier breaking changes
- v5.0.1 Removes horizontal-drag gesture that collapsed conversation pane.
Beta — feedback welcome: [email protected]