Skip to content

CCCC 0.4.52

Choose a tag to compare

@github-actions github-actions released this 09 Nov 14:02
· 30 commits to main since this release

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.