Skip to content

How It Works

Yogeswaran Amsavalli edited this page Oct 3, 2026 · 1 revision

How It Works

Clayo is one WPF project, CcxShell.csproj, with its files flat in the repo root. The namespace is still CcxShell and some names still say ccx, from the project's first draft.

Module map

File What it does
Program.cs Entry point. Answers clayo --statusline before any WPF assembly loads, otherwise starts the app.
App.xaml(.cs) Start-up: clears inherited Claude Code session variables, picks the folder, takes the single instance, opens the window and the island, keeps the login entry. Logs crashes. App.xaml holds the palette and shared styles.
SingleInstance.cs Mutex plus named pipe: a second launch hands its folder to the running window.
LoginStartup.cs The per-user Run key entry for Start at login, and its opt-out marker.
MainWindow.xaml(.cs) The window: sidebar, session rows (SessionRow), folder picker, pane switching, header buttons, Settings, close-to-hide.
SessionStore.cs Scans and watches ~/.claude/projects, reads each transcript's head and tail. Read only.
SessionLauncher.cs Builds the claude command for new, resume and fork, and picks the shell.
SessionNames.cs Names you typed, in names.json.
SessionParents.cs Which session each branch came from, in parents.json.
ConPty.cs P/Invoke for pipes, the pseudoconsole and CreateProcessW; PtyProcess runs one child with a read thread raising raw bytes.
TerminalPane.xaml(.cs) One pane: WebView2 plus xterm.js, bridged to a PtyProcess. Infers the pane's status from its output.
Assets/terminal.html The xterm.js page: rendering, input, resize coalescing, copy and paste. xterm.js and its fit and WebGL addons are vendored in Assets/xterm/.
StatusRelay.cs The --settings file each pane gets, and clayo --statusline, which passes the status line JSON on and saves a copy.
StatusStore.cs Watches the saved status files; works out the account's limits, including the fallback cache.
GitInfo.cs Branch and uncommitted line counts for open panes' folders.
StatusStrip.cs Turns status, git and settings into the header strip and footer meters; colour bands; ReserveWatch for the near-limit warning.
StatusStripView.cs Draws the strip (StatusStripView) and footer (LimitsView) in each theme.
ClayoSettings.cs The settings record, its defaults, and settings.json load and save.
SettingsPane.xaml(.cs) The Settings screen.
IslandWindow.xaml(.cs) The island window: polls the cursor, places and animates the pill, handles clicks and drops.
IslandTrigger.cs Decides when the island peeks, notifies, greets or shows the drop target. No WPF, so checks/island.cs can drive it.
IslandGlow.cs The coloured light under the island and the dwell bar at the top edge, in a click-through window of their own.
ClayoMascot.cs The voxel mascot and its moves, drawn natively so the island needs no WebView2.
checks/*.cs Runnable self-checks. See Building and Checks.

A shell, then claude

Each pane starts a shell under ConPTY (pwsh.exe -NoLogo if it is on your PATH, else powershell.exe -NoLogo) and types the claude command into it 400 ms later. When Claude exits you land on a prompt instead of the pane dying.

Before any pane starts, App.OnStartup clears CLAUDECODE, CLAUDE_CODE_CHILD_SESSION, CLAUDE_CODE_ENTRYPOINT, CLAUDE_CODE_SESSION_ID, CLAUDE_CODE_MESSAGING_SOCKET, CLAUDE_CODE_MESSAGING_TOKEN and CLAUDE_PID from Clayo's own environment. Panes inherit it, and if Clayo was started from inside a Claude Code session, CLAUDE_CODE_CHILD_SESSION alone would switch transcript saving off, hiding the new session from the sidebar and from --resume. Configuration variables (ANTHROPIC_* and the like) are left alone.

The byte bridge

Bytes cross between C# and JavaScript base64-encoded, and are never decoded to text on the way. A pipe read can split a UTF-8 character or an escape sequence in half; xterm.js reassembles them. Decoding to a string in C# would corrupt both.

  • Out: the ConPTY read thread hands raw chunks to TerminalPane. Output is held for about a frame (8 ms) and posted as one {t:"o", d:<base64>} message, because ConPTY delivers one redraw in several reads and posting them one by one let xterm paint half-drawn screens.
  • In: xterm's onData and onBinary are encoded to base64 and posted as {t:"i"}; C# writes the bytes to the pty.
  • Resize: {t:"r", cols, rows}, coalesced to 60 ms in the page.
  • Paste: Ctrl+V posts {t:"paste"}. If the clipboard holds files and no text, C# types their paths; otherwise the browser does a normal paste.

Status is read off the same raw bytes. "API Error:" and "Do you want to" are plain ASCII, and ASCII bytes never occur inside a multi-byte UTF-8 sequence, so the stream can be searched without decoding. A short carry from the previous chunk catches a marker split across two reads.

Each pane's WebView2 is disposed when the pane closes. Removing it from the window alone doesn't free the renderer.

Reading transcripts

SessionStore reads *.jsonl under ~/.claude/projects, skipping subagents folders and empty files. For each file it reads:

  • The head (up to 40 lines): the cwd, the sessionId and the first human prompt.
  • The tail (the last 96 KB): the newest ai-title and last-prompt lines, which Claude Code rewrites as the conversation grows.

A file is re-read only when its size or modified time changes, and the folder watcher is debounced by 600 ms.

Working directory from the transcript, not the folder name. Claude Code names each project folder by replacing path separators with dashes, so a path that contains a real dash can't be recovered from it. Clayo uses the cwd recorded in the transcript. The folder name is a last resort, for display only, never used as a working directory.

Branch links

claude --resume <id> --fork-session copies the parent's messages into the child and rewrites sessionId on every copied line, so a forked transcript holds no reference to its origin. Clayo allocates the child's id itself, so it knows the link the moment it forks, and writes it to parents.json keyed by the child's id (SessionParents.cs). Because the id is known up front, the branch can be drawn in the sidebar immediately instead of scraping the new id from the output.

The sidebar walks each row's parents to work out its indent (at most 8 hops, which also stops a cycle in a hand-edited file), and builds a sort key from the chain so each branch sorts directly under its parent.

Where Clayo keeps its files

Clayo reads ~/.claude/projects and never writes there. Its own files:

Path What
%LOCALAPPDATA%\Clayo\settings.json Your settings. See Settings.
%LOCALAPPDATA%\Clayo\names.json Names you gave sessions
%LOCALAPPDATA%\Clayo\parents.json Branch links
%LOCALAPPDATA%\Clayo\statusline.json The --settings file every pane gets (statusline.dev.json for a Debug build)
%LOCALAPPDATA%\Clayo\status\<session id>.json The last status line payload of each session
%LOCALAPPDATA%\Clayo\no-login-start Present when you turned Start at login off
%LOCALAPPDATA%\Clayo\crash.log Crash details
%LOCALAPPDATA%\CcxShell\WebView2\ The terminal panes' shared WebView2 data
HKCU\Software\Microsoft\Windows\CurrentVersion\Run, value Clayo Start at login

The JSON files are written beside themselves and swapped in, so a crash mid-write can't truncate them. A corrupt file is treated as empty rather than stopping Clayo from starting.

Clayo also reads, never writes: settings.json in CLAUDE_CONFIG_DIR or ~/.claude (your status line and effort), and %TEMP%\claude\statusline-usage-cache.json if your own status line script saves one.

Clone this wiki locally