CCCC v0.4.52 Release Notes
CCCC v0.4.52 keeps the resource panel across a refresh: reloading a browser tab brings back the tabs you had open, the one you were reading, a side-by-side comparison, and whether the panel was collapsed or maximized. Switching Groups no longer flashes the connection symbol or an unread mail mark. See Tabs Come Back After a Refresh and Steadier Group Switching.
It also does less work in the background. The Web no longer reads the whole history of every Group every 30 seconds to find messages for Chet, connected sessions ask the daemon less often while they are idle, and on Linux with glibc the daemon asks the allocator every five minutes to return unused pages to the system. See Less Work in the Background and Less Memory Held After Busy Periods. A Ctrl-C that arrived while the Web was starting no longer ends cccc web, cccc --headless or cccc without the terminal interface at once; see Ctrl-C While the Web Starts. This release covers the changes since v0.4.51.
Before You Upgrade
Before upgrading, let sends and recordings finish. Restore or discard waiting sends, then send or copy every unsent draft, restored ones included, and save unsaved edits in Files: all of these live only in the open page. Check whether a message already arrived before sending it again.
Upgrade the CCCC executable, restart the daemon and Web the way you usually launch them, and reload open CCCC pages. The standalone installer behind cccc update stops a running daemon and starts it again from the new version. Before a pip upgrade, run cccc daemon stop and close any foreground CCCC so pip can replace the executable, especially on Windows. A daemon left running keeps the previous version. Runtime history stays in CCCC_HOME.
- A page from v0.4.51 or earlier does not save its resource tabs, so the reload right after upgrading still starts with an empty panel. Tabs you open after that come back on later refreshes.
- Claude, Pi and OpenCode sessions that are already connected keep their current receiver, which still asks the daemon at the earlier interval, until the session is started again. They keep working with the new daemon in the meantime, so there is no need to restart them for this.
Tabs Come Back After a Refresh
Each Group keeps its own resource tabs, and refreshing the same browser tab now restores them: their order, the selected tab, the side-by-side layout, and whether the panel was collapsed or maximized. A Group's side-by-side layout also stays when you switch to another Group and back.
Only which workspace files, attachments and Presentation slots were open is kept, in that browser tab's session storage, never their contents. Each resource is read again with your current permissions when it is shown:
- A workspace file that was deleted or renamed in the meantime shows the usual error with Retry. Until the Group's folder list has loaded, a restored file shows Loading instead of saying it belongs to another folder.
- A message's reference to a Presentation slot fetches that message again. If the message no longer holds the reference, the tab says so instead of showing the slot's current card; if the request fails, the tab offers Retry.
- Attachments the ledger had not confirmed yet are left out.
Unsaved edits, reading positions and recently closed tabs are not restored. Browser tabs do not share this state, and whether a tab the browser duplicates or reopens keeps it depends on the browser.
Steadier Group Switching
Switching Groups replaces the Group's event stream. For that moment the header showed the connection symbol as reconnecting, and a member's unread mail mark disappeared and came back. The connection now stays shown as connected while the new stream opens. It shows reconnecting only if the stream has not opened within a second, and a stream that fails still shows at once. Unread counts already shown stay until fresh ones arrive. On the first visit to a Group there are no counts to keep, so its marks appear once they load, as before.
Less Work in the Background
- Messages for Chet. Every 30 seconds the Web read the whole history of every Group to find messages addressed to Chet. When the Groups' ledger indexes did not all fit the in-memory cache, these sweeps could rebuild them again and again, which cost a burst of CPU each time and raised resident memory. The Web now keeps its place in each Group's ledger and reads only what was written since. It still reads a Group in full when the Web starts or first sees the Group, when the Group's ledger files change in a way it cannot continue from (for example after the ledger rotates), and after a failed read. Pending registrations and dispatches are still retried, and the sweep still runs every 30 seconds to recover messages the live event feed missed.
- Idle sessions. A Claude, Pi or OpenCode session connected to CCCC asks the daemon for its next message less often while nothing is waiting for it: after an empty answer it waits 500 ms instead of 200 ms before asking again. Other timing, including Claude's existing notification limit, is unchanged. A message sent to an idle session can take a little longer to be picked up. Sessions that are already connected change over as described in Before You Upgrade.
- Message rendering. The actions on each message (reply, copy link, relay and the others) were rebuilt on every render of the chat, so each new message re-rendered every visible message and its action bar. They now keep one identity, which removes those re-renders. What the actions do is unchanged; each uses the Group and settings current when it is clicked.
- Agent reads and daemon replies. Agents reading message history through MCP get the newest page without the daemon first collecting every message they can see, and the daemon no longer copies most results once more before replying. Results are unchanged.
Less Memory Held After Busy Periods
On Linux with glibc, memory the daemon freed after a busy period could stay with the process, so its resident memory could stay near the size of its busiest moment. The daemon now asks the allocator every five minutes, the first time five minutes after it starts, to return unused pages to the system. When the daemon runs inside cccc, cccc web or cccc --headless, the whole process is trimmed, Web included; a daemon started with cccc daemon start trims only itself. macOS and Windows are unchanged.
This is not a memory limit: memory still grows while the daemon is busy, and pages it returned take a little time to reuse. The daemon diagnostics log records the first trim after the daemon starts and any trim that takes longer than 200 ms, with how long it took and the resident memory before and after.
Ctrl-C While the Web Starts
A Ctrl-C after the daemon was ready and while the Web was starting, including just as its Local: address appeared, ended cccc web, cccc --headless and cccc without the terminal interface at once. Such a Ctrl-C now starts the usual stop, and a second one still forces the exit. How these commands handle a Ctrl-C before their daemon is ready is unchanged.
The terminal interface on Linux and macOS had a similar gap right after it began watching for signals, and now listens for Ctrl-C from that moment.
Building From Source
./scripts/build_package.sh and build_package.ps1 now name the archive they write in dist/ after the commit, for example cccc-v0.4.52+<commit>-x86_64-unknown-linux-gnu.tar.gz, adding -dirty when the tree has uncommitted changes and using local outside a Git checkout. A local build therefore no longer replaces a release archive of the same version, and rebuilding replaces only the archive of the same name. The executable is still target/release/cccc, or target\release\cccc.exe on Windows.
Validation Scope
Release preparation ran these checks on one frozen tree, all but ruff in isolated homes: the Rust workspace tests (113 targets, 3,927 passed, 0 failed, 10 ignored), rustfmt and clippy, the CI test profile with nextest 0.9.148 and real Chrome forbidden (3,877 passed, 60 skipped), the daemon self-launch tests (3 passed), the Web check and the full Web tests (4,939 tests in 657 files), and the repository's Python tests (126 passed) and ruff. Corrections made afterwards changed only these release notes, and git diff --check and the Python tests were run again on them.
The Ctrl-C change was checked in a separate copy of the source: two new process tests each run one signal watcher alone in a fresh process and raise an interrupt before the watcher first runs. Making either watcher register only once it first runs again made its own test fail with the process ended by the interrupt, while the other test passed. The headless startup test that failed once on CI passed in the same copy.
The restored panel was walked in an isolated headless Chrome, with the Web source served against an isolated v0.4.51 backend: the side-by-side layout with one file on both sides, attachments, live and quoted Presentation slots, a quote that could not be recovered and a failed request with Retry, a maximized panel, Group switches, a deleted file, a new independent browser tab, and the two sides stacked top and bottom at 390 pixels wide, with no page errors. With the Group request slowed to 0.8 seconds, the notice about another folder appeared once on refresh before the change and not after it.
Group switching was checked frame by frame in headless Chrome, with a member who had unread mail: over six switches, the reconnect symbol had stayed on screen for 50–100 ms on each switch, and the mail mark had been gone for 50–100 ms on each of five returns to a Group. With the change neither occurred, on the Web source and again on the Web of a trial build containing it. On a test Group of 150 messages, one incoming message re-rendered about 330 components instead of about 4,200.
The Chet sweep was measured in isolated homes with nine synthetic Groups holding 56.8 MB of ledgers, one 180-second run per case. With the previous Web and the sweep running, the process used 0.19–0.38 s of CPU in the two seconds around each 30-second mark, against 0.03–0.05 s with the sweep held inactive, and its resident memory ranged from 346 to 597 MiB after the first 10 seconds, against 217 MiB. With a trial build containing this change and the sweep running, the same windows used 0.04–0.06 s, and resident memory stayed between 241 and 244 MiB. On a long-running installation, a one-second sample over six minutes after switching to that trial build showed none of the 30-second bursts seen before. Tests run the sweep against a stand-in daemon, and five mutations of the change (dropping either reread, not keeping the place, keeping events from before an import marker, and letting one failed read stop the whole sweep) each made a test fail.
With five idle Claude sessions, one 60-second run per version, the daemon used 0.62% of one CPU core instead of 1.27%, and each session's receiver 0.10–0.12% instead of 0.27–0.30%. Ten first notifications after idle periods appeared in the receiver's output 12–404 ms after they were sent, instead of 31–139 ms.
Allocator settings were compared on the v0.4.51 package in isolated homes under a synthetic load of 16 paced clients, two runs each. With default settings, resident memory after a 30-second rest was about 985 MiB. In a matched pair with a small probe loaded, two trims during the load took 42 and 59 ms and each brought resident memory down to about 280 MiB, after which the load grew it again. Limiting glibc to two arenas held memory at about 305 MiB but cost about 58% more CPU per request, so it was not adopted. On a long-running installation, a trial build with the trim but without the Chet sweep change ran for about six and a half hours. Its diagnostics log recorded the first trim, which took 113 ms, and 22 later trims that took over 200 ms, up to 415 ms; each brought resident memory from 1,382–2,223 MiB down to 286–433 MiB. On the same installation with the Chet sweep change, the first trim took 24 ms and brought resident memory from 517 to 296 MiB, and the diagnostics log recorded no later trim over 200 ms in the following hour and a half.
The new message history path was timed against the old one in one release test binary on a synthetic Group of 37,500 events: the default page's median went from 2.0 ms to 0.3 ms. Reading the latest 120 messages, their delivery statuses or 50 messages of history requested 12–21% fewer bytes from the Rust allocator. The archive helper's tests cover tar and zip archives, and four mutations of it each made a test fail.
The Linux x86_64 package built for this release, cccc 0.4.52, was checked in isolated homes: its version and the files in its archive; the 33 scripts and stylesheets its own Web serves, byte for byte against the Web build; and six Group switches on its own Web, frame by frame, with the connection shown as connected in every frame and the mail mark kept on all five returns to a Group. Its host, which runs the daemon and the Web in one process, did not trim at startup, recorded its first trim in the diagnostics log 300 seconds after starting, returned the same entry through the daemon's diagnostics, and ended normally on Ctrl-C.
This is not complete live acceptance. None of these changes was tried here on native Windows or macOS, and the PowerShell package script was reviewed but not run. The panel and Group switching were not tried in Safari, Firefox or on real phones, and idle sessions were measured with Claude receivers only.