# Changelog ## 0.95.9 - 2026-11-06 13:59 PST **Pairing cannot be jammed by a stranger, and half-typed drafts and prompts stay with their own tab.** - Anyone who could reach the tunnel could keep device pairing broken by sending junk codes: twelve a minute filled a shared limit, and five wrong guesses burned the live code. Both counters are gone; a 30^8 code space, one live code and a two-minute life keep guessing hopeless (#40). - An expired pairing code could work again if the system clock stepped back. Expiry now uses a monotonic clock, and an expired code is dropped (#41). - A draft typed and then left with a fast tab switch, or a closing page, was sometimes lost or overwritten by an older save. Drafts now save in order, flush when the page hides, and show a message if a save fails (#32). - A prompt sent from one tab, then a quick switch to another, could send its repeats, its auto-title and its draft clear to the tab in front. The send now stays with the tab it came from (#33). - A folder set to match `/srv/work` also took sessions in `/srv/workshop`. Matching is now by whole directory, and a new session made with no folder lands in Ungrouped instead of being filed by directory (#35). - Deleting every folder now stays deleted after a restart, instead of the default folders coming back (#34). - Project search keeps its time and size limits inside one very large directory, and lists the named directory itself when it is a project (#36). - Scratch cleanup stays inside the project, and git diff reads are bounded (#31, #37). ## 0.95.8 - 2026-11-02 09:56 PST **A zoomed pane stays inside its box, and the site says there is an Android app.** - A zoomed pane drew about 3px past the bottom of its box, because the row count included the pane's padding. It now measures from where the terminal starts, as the width already did. `redraw_check.py` proves it (CLQ-106). - useclique.dev has a new question, "Is there a phone app?", in all six languages, linking the Android source and its F-Droid repository (CLQ-28). - Tests only: a visual check that a notification link opens its session (CLQ-58), and two visual checks that wait on a condition instead of a fixed time. ## 0.95.7 - 2026-11-01 09:52 PST **Line height and letter spacing for the terminal, and a click that went missing.** - Settings, Appearance has two new sliders: line height (100 to 160%) and letter spacing (0 to 4px). Both are saved on the server and follow you to other devices. Changing either refits the pane, and tmux gets the new size at once; `redraw_check.py` proves it (CLQ-67). Ligatures are not included, since xterm needs an extra addon for them. - A drag or double-click in a pane that was released with no click after it, for example with the pointer outside the window, left a catcher armed that swallowed the next click anywhere in the panel. It is now cleared after every release (CLQ-107). ## 0.95.6 - 2026-10-30 12:32 PDT **Bigger tap targets on a phone, and two more slices off the biggest files.** - Every small control the phone layout shows is now at least 32px to a thumb: the tab's close and settings buttons (were 17 by 13), the repeat stepper, attach, read-only lock, full screen, and the side rails. Desktop with a mouse is unchanged. `visual_check.py` now measures every visible control at phone width, so a new undersized button fails there (CLQ-65). - Session peek, attention, groups, checkpoints, export and notes routes moved out of `app.py` into `clique/session_routes.py` (CLQ-98, fourth slice). The HTTP smoke test now calls `/peek`, which nothing checked before; the move briefly broke it and only the type check noticed. - The Notes, Git, Session info and Export panes moved out of `app.js` into `web/panes.js` (CLQ-99, fourth slice), about 480 lines. ## 0.95.5 - 2026-10-30 11:24 PDT **Two more slices off the biggest files, and the last Android review fix.** - Model provider routes moved out of `app.py` into `clique/provider_routes.py` (CLQ-98, third slice). The API drift check now reads every route module, so a moved route is still checked against `API.md`. - Model providers, the command palette and pane drag and zoom moved out of `app.js` into `web/providers.js` (CLQ-99, third slice). About 1,390 lines shorter. - Android 0.10.5: a slow session preview can no longer overwrite a newer one. ## 0.95.4 - 2026-10-30 11:08 PDT **Two more slices off the biggest files, and two Android fixes.** - File, artifact, paste and upload routes moved out of `app.py` into `clique/file_routes.py` (CLQ-98, second slice). About 250 lines shorter. - The file, transcript and usage preview sheet moved out of `app.js` into `web/file-preview.js` (CLQ-99, second slice). About 830 lines shorter. - Android 0.10.4: a permission alert gets a third button that sends your first bar snippet (CLQ-59), and the prompt box saves its draft a second after you stop typing, not only when you leave the screen. ## 0.95.3 - 2026-10-30 10:08 PDT **Two of the biggest files get smaller, with no change in behaviour.** - The WebSocket terminal code moved out of `app.py` into `clique/terminal_handler.py` (CLQ-98, first slice). `app.py` is about 350 lines shorter. - The sidebar's plan usage and machine stats moved out of `app.js` into `web/sidebar-status.js` (CLQ-99, first slice), loaded just before it. - Android 0.10.3: every reply the app reads is capped at 32 MB instead of being held whole whatever its size (CLQ-100), and after a reconnect a late frame from the old connection can no longer write into the new pane (CLQ-104). - Gemini usage: looked into and not added. Its quota endpoint needs a Google sign-in token that expires hourly and only the Gemini CLI can renew; the Antigravity bar already shows the Gemini model quota. ## 0.95.2 - 2026-10-29 16:30 PDT **Plan usage says when it is old, and fetches the fresh numbers itself.** - The panel answers a stale reading at once and probes behind it, so the sidebar's usage panel now takes one more look four seconds later and shows the fresh bars without waiting five minutes for the next poll. - A reading older than five minutes (say, after a sign-in expired) is captioned "checked 12m ago", so old bars no longer pass for current ones. - When the panel itself cannot be reached, the usage panel says so instead of claiming no CLI reports plan usage, and a blip keeps the last bars. - Android 0.10.2: the Plan usage screen honours a reset time's offset (it read every time as UTC), pulls to refresh with a fresh probe, and gets the same stale caption and recheck. ## 0.95.1 - 2026-10-29 14:57 PDT **Plan usage probes hardened, from the estate audit.** - A usage probe no longer follows redirects. The SSRF guard only vets the first URL, so a redirect could have carried the CLI's token somewhere else; it now reports "HTTP 302" and stops. - A reset time no clock can hold (a huge or broken timestamp) shows as no reset time instead of a 500, and any unexpected reply clears the in-flight marker, so one bad answer cannot wedge that CLI's bars. - Cold probes run side by side, so one slow CLI no longer delays the rest. - Reopening a session replays up to 20,000 lines of history (was 5,000), and the Android pane keeps 20,000 (was 8,000), so a long session scrolls back further. Only sessions started after the history-limit was raised can hold that much. - Release headings written with a plain hyphen are parsed and stamped, and the stamper finds releases whose commit starts with the version, so old entries keep their real release time instead of today's. - Android 0.10.1: notification permission buttons no longer answer each other, the terminal stops touching a closed pane, draft saves survive leaving a session, and shared images are read with a size cap. ## 0.95.0 - 2026-10-29 13:27 PDT **Scrollback that survives a redraw, file links that open, sturdier plan usage.** - Claude Code clears tmux's scrollback (ESC[3J) on every full redraw, which is why a long session could not be scrolled back on the phone. A CLI can now set `keep_scrollback = true` in `clis.toml`; it runs under a small pty shim (`clique/keepscroll.py`) that drops only that one sequence. On for Claude. Sessions started before the upgrade keep the old behaviour until restarted. - A CLI can list its `models` and a `model_cmd` (`/model {model}`) in `clis.toml`. Both are on the session in `/api/state`, so a client can offer a model switcher without knowing the vendor. Filled in for Claude, Grok, Antigravity; Gemini and Codex open their own picker. - File links in the pane now open files under the system temp folder (CLIs print draft and scratch paths there). Read only; writes keep the strict fence. A refused path says why instead of looking missing. - Plan usage: an old reading is answered at once and refreshed in the background, so a slow probe (Antigravity shells out, ~2-4 s) no longer holds up a poll. A failed refresh keeps the last good bars with the error beside them. An expired sign-in (HTTP 401/403) says to open the CLI and sign in again. A slow background refresh can no longer overwrite a newer forced one. - Android 0.10.0 uses all of this: flick scrolling with a jump-to-latest button, a Model item in the session menu, and tappable file paths. ## 0.94.0 - 2026-10-26 14:14 PDT **Hardening pass from a full Codex review of the panel, browser, phone and desktop.** Server: - Uploads open the drop directory by file descriptor and check it is still inside the session directory, so a symlink swapped in mid-upload cannot write outside it. - Login throttling is one locked step (check and record together), no longer racy under concurrent attempts, and old entries are dropped instead of piling up. - A request body over the limit now gets a 413 and the connection is closed, and a body that is not a JSON object gets a 400 instead of a 500. - LLM calls refuse redirects (SSRF) and cap the response at 4 MB. - A saved state file with the wrong shape falls back to the backup instead of blocking startup. A bad timestamp in one usage window no longer disables auto-resume for the rest. - File saves use a unique temp file and replace atomically. Creating a session that fails part way now removes its worktree and tmux session. Browser: - Settings writes are queued and report failure (a toast, not a false "Applied"). A failed send keeps your draft. - A slow poll can no longer overwrite a newer one, artifacts cannot land on the wrong session, dead terminals are closed, and polling pauses while the tab is hidden and catches up the moment it is shown again. - Saving a file no longer repaints over a different file, and overlapping saves cannot re-enable Save early. New checks: `tools/hardening_check.py` (12 server checks, each proved by breaking the fix) and more cases in `tools/frontend_check.js`. Not in this release: the Windows desktop trust gate and signed updater are written but held until the signing key and a Windows smoke test exist. ## 0.93.0 - 2026-10-13 13:58 PDT **Suggested commands are now click to copy.** - A line a CLI prints as `! ` (the convention for "run this in your own shell") is now a click target, like a URL already was: click it to copy the command, no select-and-paste needed. - A wrapped command is stitched back into one line before it is matched or copied, so a long suggestion does not get cut at the column it wrapped on. - The phone long-press menu's "Copy this line" had the same wrap bug (it only ever read the one row under your finger); it now stitches too. ## 0.92.0 - 2026-10-13 11:52 PDT **Four fixes from a Codex code review.** - API token store: creating or revoking a token now reloads the file first, so the panel process and a concurrent `token` CLI call no longer stomp on each other's writes. - WebSocket frames: the server now rejects a data frame that starts before the previous fragmented one finished, or a continuation frame with nothing to continue, per RFC 6455. Previously the two could be merged into one message. - Theme limit: hitting the 40-theme ceiling now clears the active theme and rotation pool if the evicted theme was either, instead of leaving them pointing at a theme that no longer exists. - `API.md`: corrected the WebSocket text/binary frame description (it had them backwards) and documented the `routes` and `features` fields that `GET /api/llm/providers` already returned. ## 0.91.0 - 2026-10-13 10:20 PDT **Boxed CLI text stays the size you set it to, across sidebar collapse and expand.** A boxed CLI (Claude Code, Codex, Gemini's own TUI) widened its grid to fill the extra room a collapsed sidebar freed, so expanding the sidebar back needed a smaller zoom than before, and the text came back shrunk. Its column count now stays fixed at every pane width, not just the zoomed ones, so collapsing and re-expanding leaves it where it started. Same fix covers the desktop client, which renders the same panel. ## 0.90.0 - 2026-10-11 10:41 PDT **The shortcuts bar scrolls properly, touch or mouse.** `#quickRow` kept touch gestures and vertical page scroll separate with `touch-action`, and contained horizontal overscroll so a drag that reaches either end doesn't bounce the whole page. A left/right fade now shows when more commands sit off-screen, and disappears once there are none. On a desktop or the WebView2-based Windows client, a plain mouse wheel over the row now scrolls it sideways instead of doing nothing: Chromium doesn't do that translation on its own when there's no vertical overflow to consume. Native horizontal trackpad scroll and ctrl-wheel zoom are untouched. No desktop-client change needed: it renders the same panel, so the fix applies there too. ## 0.89.0 - 2026-10-11 10:29 PDT **Bigger tap targets on the mobile shortcuts bar.** The row of one-tap snippet and quick-command pills above the input box already worked on phone, but the buttons were 24px tall, well under the 38px used everywhere else on phone (`#keyrow`'s on-screen terminal keys). In a packed, mostly-offscreen row that made a scroll swipe easy to misread as a tap. Buttons now match `#keyrow`'s size. ## 0.88.0 — 2026-09-30 11:58 PDT **Overnight work picks itself back up.** When a session runs out of plan (Claude's "limit reached, resets 3pm"), it used to sit there until someone typed "continue" after the reset. A folder can now do that itself: open the folder's menu and turn on "Resume after usage limit". Once the usage probe says the limit has reset, plus a minute, the panel types "continue" into each session in that folder that stopped on the limit. It fires once per stop, never on a session waiting for a permission answer, and only for a CLI that has a usage probe, because without one there is no honest reset time to wait for. Off by default, per folder. What counts as "stopped on a limit" and what gets typed are `limited` and `after_limit` under a CLI's `attention` block in `clis.toml`. ## 0.87.0 — 2026-09-30 11:21 PDT **Typing feels instant on a slow link.** Every letter used to wait a full round trip to the server before it appeared, so on a phone or a distant box the pane lagged behind the keyboard. Now a typed letter is drawn at once, in a layer above the terminal, and is replaced by the real one when it arrives. It only guesses where that is safe: plain letters on the normal screen with a visible cursor. It never guesses on a line asking for a password or PIN, in a full-screen program, or after Enter, arrows or a paste. If the program is not echoing at all, it stops guessing until the next Enter. Settings, Keyboard, "Show what you type at once" turns it off; `local_echo` in the settings API. ## 0.86.4 — 2026-09-30 10:57 PDT **A phone can no longer drag the panel sideways.** On a touch screen the page was 13px wider than the screen, so a thumb could slide the whole panel left and bounce it back. The cause was the enlarged, invisible tap area on the right-hand rail's buttons, which reached past the screen edge. The panel now clips anything past that edge; nothing visible lived there, and the buttons keep their full tap area. The visual check now opens the panel as a real touch phone and fails if the page is wider than the screen. ## 0.86.3 — 2026-09-30 10:33 PDT **The desktop update control says what it will do, and is hard to miss.** When a new desktop build is downloaded and verified, the version line now shows a highlighted "Update to " pill instead of a small link. It moves once, for two seconds, when it first appears, then stays highlighted; a repaint does not replay it, and reduced-motion settings get a plain fade. Clicking it still installs and relaunches straight away, and a failed install leaves the old version running with a retry. ## 0.86.2 — 2026-09-30 10:32 PDT **A usage probe that fails now says why.** Before, a CLI whose plan-usage probe failed showed exactly the same blank as a CLI with no probe at all, so there was no telling "not configured" from "broken". Now the usage panel shows one quiet line under that CLI, such as "Couldn't read usage: no token (not signed in?)", "HTTP 401", "command exited 1" or "reply is not JSON". The API's `/api/usage` entry carries the same text as `error`, with empty `windows`. A CLI with no probe declared still gets no entry. Failures stay cached for the same five minutes as successes, so a box with no key still stops asking. ## 0.86.1 — 2026-09-30 09:53 PDT **The scroll wheel stopped overshooting.** Every pane's wheel handler floored each event to at least one scroll tick, so a trackpad or a precision mouse sending dozens of tiny wheel events for one physical gesture fired a tick for every one of them, several times more scroll than the same motion on a notched wheel — the pane flew past what you meant to read. Wheel pixels now accumulate and only spend a tick once the total crosses a threshold, the same approach the touch handler already used. Firefox's line-mode wheel deltas (deltaMode 1) are converted to pixels first, since the old code's per-event floor happened to read those correctly by accident and the new accumulator would otherwise have needed eight notches to register one. Applies to every session's pane, on both the normal screen (own scrollback) and the alternate screen (forwarded to the app). ## 0.86.0 — 2026-09-22 16:00 PDT **One house loader, everywhere the panel makes you wait.** CLQ-46. Four small cells walking, `currentColor` so it tints with whatever text or button it sits inside — the transcript sheet ("Reading…"), the file sheet ("Looking…"), the theme generator button ("Making it…"), and the update badge's busy state ("Installing…") all draw the same one now instead of sitting there as plain text. CSS only, no dependency, a slower fade under `prefers-reduced-motion` rather than nothing. The update badge's dot used to just stop pulsing while installing — a wait with no motion reads as hung, which is the whole reason this card existed — so busy now swaps it for the loader instead of a still dot; the CSS rule that only ever applied to that now-impossible combination is gone with it. ## 0.85.0 — 2026-09-22 15:50 PDT **The font picker says which font you actually got.** Settings → Font now shows "Using: Consolas" under the picker — the first face in the chosen stack the browser can actually draw, not just the one clicked. A font that is installed but missing its regular weight (the bug this traces back to: a machine with Ubuntu Mono's italic face but no regular one, drawing every line slanted with no visible cause) used to substitute silently; now the substitution is a fact on screen. Resolved with a canvas text-width comparison rather than `document.fonts.check()` — that API reports a bare system font name as available even when nothing by that name is installed, since a generic fallback can always draw the string, which would have made this feature always name the first stack entry and never catch the actual bug. Verified against the real running panel: the stored setting ("menlo", not installed on this Linux box) resolves to the true fallback, DejaVu Sans Mono, not to "Menlo". Built with Grok CLI from a full spec; reviewed line by line, including catching and verifying that deliberate deviation from the spec before trusting it. ## 0.84.0 — 2026-09-22 15:29 PDT **A session shows when its CLI has shelled out to another one.** A Claude Code window that runs Grok or Codex headless to do sub-work now says so: a small stacked badge of the sub-CLI's own logo, beside the session's own mark in the sidebar row and on the tab, with a tooltip naming which ones. Built entirely from process state already being read for per-session CPU and memory — `sysinfo.py`'s existing `/proc` walk kept the executable name it was already parsing and throwing away, so this is one more thing read off a pass that already happens, not a new one. A match is a descendant only, so a session's own top-level process never flags itself, and a bare `bash` shell is excluded on purpose since it would light up almost everything. Comm names are a kernel-truncated 15 bytes, so this is a heuristic name match like the attention regexes, not a certainty, and shares the same up-to-8-second cache lag as the CPU/RSS numbers it rides alongside — a very short-lived headless call can finish between two samples. Verified against a real, running session with a real spawned process, through the live API. Built with Grok CLI from a full spec, reviewed and verified before shipping. ## 0.83.0 — 2026-09-22 14:51 PDT **Antigravity reports plan usage too.** No plain REST endpoint exists for it: the real quota call (found by reading strings out of the `agy` binary) is a protobuf Connect-RPC to Google's internal Code Assist service, authenticated through the OS keyring rather than a token file — out of reach of the url+token_file probe Claude, Grok and Codex already use. `agy` itself already knows how to ask and print the answer as JSON, so `usage.py` gained a second, equally declarative probe kind: a `cmd` in a CLI's `.usage` block runs that argv and reads its stdout, same TTL and same silence-on-failure as a URL probe. Its reply also reports what is *left* rather than what is spent, so `_dig()` picked up two small, generic (not Antigravity-specific) abilities alongside the `*` wildcard from 0.80.0: a `key=value` step to find one item in a list of named buckets by its id, and a `remaining` window key as the other side of `percent`. Verified against a live account. Gemini CLI still has no viable path — its pool moved to Antigravity for consumer accounts, confirmed again this pass — so it stays out. ## 0.82.0 — 2026-09-22 14:34 PDT **The plan usage panel is a real panel now, not a debug dump.** Each CLI gets its own card: its real logo, its label, a bar per window, and — visibly, not only in a tooltip — when it resets, since a phone has no hover to reveal that from. A window at 90% or past pulses instead of just sitting red, so the one state worth a glance without reading the number reads as one. The decorative watermark behind the sidebar's empty space is gone; it sat right behind this panel and, with a few CLIs open, cluttered straight through it. The compact copy that used to sit in the top status bar is retired too, since the sidebar panel already covers every installed CLI and having the number in two places said nothing the one place didn't. Fixed alongside it: the panel could get stuck showing a CLI's raw id and a "?" badge instead of its real name and logo if it auto-reopened (from a saved preference) before the CLI list had loaded — it never redrew once that arrived, only on the next open or five- minute poll. It now redraws on every state poll, a no-op while closed. ## 0.81.1 — 2026-09-22 13:26 PDT **Fixed: the 0.79.0 "compacting" indicator never actually fired.** Two bugs, both real, found only by forcing a live compaction and watching `/api/state` rather than trusting the code read. First: detection was gated behind the same 8-second settle delay waiting/error use to rule out an ordinary output burst — but a real compaction is often faster than that, so most of the time the event was over before detection was ever allowed to look. Compacting now gets checked the moment the pane goes busy; there is nothing to debounce it against, unlike a burst that might transiently look like a question. Second: the pane-text search took the last 40 lines of the *captured pane*, not the last 40 lines that actually had anything on them — a terminal taller than what is currently drawn (ordinary, not an edge case) pads out with genuinely blank rows, which pushed the status text out of the search window entirely. Both were latent since 0.79.0 shipped; error detection shared the same blank-padding bug. Verified against a real, running session this time, not a forced client-side value. ## 0.81.0 — 2026-09-22 12:25 PDT **A bar snippet can carry its own colour.** Settings → Snippets gets the same swatch-plus-reset control the CLI rows already use — pick a colour and the one-tap pill on the shortcuts bar tints to it instead of the flat accent colour every custom pill shared before. Cleared, it goes back to that default. Stored as a plain hex string, validated the same way a folder's or a CLI's colour already is, so an XSS-shaped value is dropped rather than landing in a `style` attribute. CLQ-78. ## 0.80.0 — 2026-09-22 11:58 PDT **Grok CLI reports plan usage too, the third after Claude and Codex.** 0.78.0 called it closed: Grok's stored credential looked like it was not a usable API key for a balance endpoint. It turns out `~/.grok/auth.json` does hold an OAuth token, just keyed by the issuer and client id rather than a fixed field name, and that token reads the same weekly-credit endpoint (`cli-chat-proxy.grok.com`) the CLI's own usage screen calls. `usage.py` gained one generic step for it: a `*` path segment takes a dict's one value regardless of its key, so a probe can name a credential-cache shape it does not otherwise know. Verified against a live account. Undocumented on xAI's side, same as it was for Claude, so this can go quiet if they change it: same silent-failure rule as any other probe. ## 0.79.0 — 2026-09-21 16:54 PDT **A session compacting its context gets its own mark, instead of looking like any other spinner.** A new ring state, `compacting` — its own ring animation, its own tab tooltip, its own row in the tab-overflow menu — sits between "asking" and "working" in priority: not a question, so it does not need you, but worth naming rather than folding into a generic "working" that tells you nothing. Detected the same way "waiting" and "error" already are: a regex against the pane's own visible text, declared per-CLI in `clis.toml`, with a generic default (`"(?i)compacting"`) that needs no per-CLI config since most agentic CLIs use the word. Compaction also spins the browser tab's favicon — the one signal a browser renders outside the page itself, and the point of the whole feature: catching it from a backgrounded tab without opening the session to check. ## 0.78.0 — 2026-09-21 16:34 PDT **Codex CLI now reports plan usage too, the second CLI after Claude.** `codex login` stores a ChatGPT OAuth token, and that same token reads usage straight from ChatGPT's own backend (`chatgpt-com/backend-api/wham/usage`), verified against a real install. Its reset time comes back as Unix seconds rather than Claude's ISO string; `usage.py` now accepts either shape and always hands the browser ISO, so no CLI's probe has to know or care which one Claude happens to use. Checked Grok, Gemini and Antigravity too. Still nothing there: Grok's stored credential isn't a usable API key for a balance endpoint (only a real CLI invocation would show it, and that would spend real usage just to check it); Gemini's credential file is encrypted at rest, no readable token at all; Antigravity's token parses fine but no usage endpoint for it is known. None of the three block anything, they simply do not show a bar. ## 0.77.0 — 2026-09-21 16:04 PDT **Plan usage for every installed CLI, not just the one running in front.** Bottom-left of the sidebar, above the version footer: a "Plan usage" row that opens into a bar per CLI, reusing the same meters the status bar already draws for the active session. Collapsed by default, and opening it is what triggers the wider fetch (`GET /api/usage?all=1`) — the routine poll still only asks about a CLI with a session open, so a panel nobody opens costs nothing extra. A CLI installed but not currently running still shows, dimmed and tagged "idle": a week's usage does not reset just because the session closed. Today that is Claude Code only — the only CLI with a working usage probe. Codex, Grok, Gemini and Antigravity are tracked (CLQ-75) but none has a probe yet, so they simply do not appear rather than showing a broken one. ## 0.76.0 — 2026-09-21 14:02 PDT **The desktop update button moved next to the version number, where it should have been from the start.** The icon redesign in 0.3.16 put it in a floating circle bottom-right of the whole window — not what was asked for, and not tied to anything. It's now drawn by the panel itself, right beside "v0.76.0", the same place the desktop shell already announces itself ("desktop 0.3.16"). Same click-to-install-and-restart behavior, no second confirmation. The desktop side (`clique-desktop/update.js`) shrank from a self-built floating card to just setting state and asking the panel to redraw — the panel already knew how to draw things next to its own version. ## 0.75.0 — 2026-09-21 13:23 PDT **A read-only MCP server, so any AI agent can drive CLIque, not just Claude Code.** `skills/drive-clique/SKILL.md` already told Claude Code how; this puts the same five reads (session list, one session, wait for idle, peek the pane, read the conversation) behind a real MCP server any client can talk to. Deliberately read-only for now: nothing here can type into a pane, start, kill, or resume a session. That needs a policy model that does not exist yet, and shipping a way to type into someone's live agent session ahead of that policy would be the wrong order to build it in. Stdlib only, matching the rest of this repo: no `mcp` package, the JSON-RPC framing is hand-rolled. `CLIQUE_TOKEN=... python3 -m clique mcp` to start it. Docs: `skills/drive-clique/mcp.md`. Built with Grok CLI, reviewed for real before landing, not just tested: confirmed the token is never logged or echoed anywhere, confirmed the pane read cap actually clamps rather than just being documented, and checked the protocol shapes against the real MCP spec rather than trusting a test Grok wrote against its own implementation. Added one thing the review flagged as missing: a smoke check that fails if a future change ever adds a write call to this file, so read-only stays true on purpose, not by nobody touching it. ## 0.74.0 — 2026-09-21 13:06 PDT **Session templates.** A saved combo of CLI, working directory, folder, name and a starter prompt. Settings has a Templates tab, same shape as Snippets. Pick one in New Session and it fills the form; the starter prompt gets typed in and sent a couple seconds after the session opens, once the CLI is actually up. Asking for a worktree still works even if you never touch the worktree checkbox yourself, a branch name gets made up from the session name plus the moment it was created so using the same template twice never collides. No folder picker in the Templates row yet, so a template made through Settings always lands Ungrouped like any other new session. Worth adding if it turns out to matter. ## 0.73.0 — 2026-09-21 12:50 PDT **CPU per session, next to the memory number that was already there.** Which session is eating the box was previously a guess: `/api/state` carried `rss` (resident memory) per session but nothing about which one was actually busy. Same process-tree walk now also reports CPU percent, cached on the same 8-second cycle so nothing new gets read from `/proc`. Shows on the session row and in the detail panel, next to memory. A reap advisor (idle time plus these two numbers deciding what to clean up) is the natural next step, not built here. ## 0.72.0 — 2026-09-21 12:23 PDT **A custom quick command is now a snippet, not its own separate thing.** 0.71.0 shipped one string that had to be both the button's label and the text it sent, which reads fine for `/cost` and terribly for a real saved instruction. Snippets already had the label-vs-text split; now a snippet just carries a `bar` switch that also shows it as a one-tap button, so the same list handles both a typed expander and a bar button, or one snippet doing both. Adding one from the bar's `+` opens the same Settings > Snippets editor a typed snippet uses, not a separate prompt. A bar button is tinted so it reads as yours at a glance, and its `×` asks first, the same confirm sheet every other delete in the app uses, rather than removing on the tap. Anyone's already-saved custom commands come back as bar-shown snippets automatically, once, on the next load. ## 0.71.0 — 2026-09-21 11:34 PDT **Your own quick commands, not just clis.toml's.** The one-tap row above the prompt box was config-only: whatever `quick_commands` a CLI declared, and nothing else, editable only by hand-editing clis.toml on the server. A `+` at the end of the row now adds one of your own, per CLI, the same idea as a snippet but a tap instead of a typed trigger — and it works for any CLI, not only the ones clis.toml already gave a row to, since a shell with nothing built in still gets the `+`. Each carries its own small delete control, so removing one is a tap, not an edit to a config file. Claude Code's own row also picked up `/cost`. ## 0.70.1 — 2026-09-21 11:25 PDT **Scrollback goes back 2.5x further.** tmux's own history limit and xterm's buffer were both 20,000 lines; both now hold 50,000. Raised together on purpose — tmux keeping more than a client can render, or a client asking for more than tmux kept, are each scrollback nobody actually sees. ## 0.70.0 — 2026-09-21 11:07 PDT **A drag past the pane's edge keeps going.** Selecting more than fits on screen meant letting go, scrolling by hand, and starting a new selection to join to the old one. A drag that reaches the top or bottom edge now scrolls that direction on its own, the way it does in any text editor, and keeps stretching the selection into what it reveals. **Arrow keys move through a search.** Typing in the sidebar's search box narrowed the list but left you reaching for the mouse to open a row. Up and down now step through the results, Enter opens the highlighted one, and the highlight tracks a position in the list rather than a session id, since a keystroke can re-run the whole query out from under it. ## 0.69.0 — 2026-09-19 09:47 PDT **A worktree stops failing its first command.** A fresh git worktree has no `.env`, no `node_modules`, no venv, and until now the first thing an agent saw in one was its own build breaking, which reads as CLIque having broken the repo rather than as a checkout nobody set up yet. Two files, both optional, both ordinary files in the repo rather than settings in CLIque: `.clique-setup`, run once in a new worktree before the CLI starts, output visible in the pane, a failure reported and never fatal; and `.clique-copy`, an explicit list of files to carry over from the primary checkout first — never a glob, so "copy the env file" cannot quietly become "copy every local file including last week's credentials dump." A path leaving the checkout is skipped, not followed. Because both are tracked files, a worktree made on any machine gets them for free; nothing to configure per box. ## 0.68.5 — 2026-09-19 09:41 PDT **Cleanup pass on the last two releases, from ponytail-review.** No behavior change, three simplifications: - `quick_commands` is now on the session row itself, the same way `modes` and `mode_label` already are — the panel front end no longer scans `state.clis` by id to find the active CLI's commands, it reads `s.quick_commands` directly. - The quick-commands row builds its buttons in one pass with `document.createElement`, not a `innerHTML` string-join followed by a second pass over the DOM to attach handlers. Drops the `escapeHtml` call too — `textContent` needs no escaping. - `GET /api/briefing`'s row builder was twelve lines of `"x": row["x"]`, each field name typed twice. Now one tuple of field names, copied through with a dict comprehension. ## 0.68.4 — 2026-09-19 09:09 PDT **GET /api/briefing: what needs you, in one call.** Every session actually asking for a person, not every session. Filtered to the two states the attention ladder already computes as "needs a reply" (waiting, error), each row carrying its branch, dirty flag, memory use, half-typed draft and the last 20 clean lines of the pane, the same frame-filtered capture /peek uses. Nothing new is measured or invented: this reshapes what sessions_view() already knows. The capture step moved into one shared function, used by both /peek and this. Fixing it here meant catching a real bug in the move: a failed capture was briefly getting cached as if the pane had genuinely said nothing, which would have frozen a transient tmux hiccup in place until the pane's next output. Confirmed with the old and new code side by side against a capture that always fails: 3 retries before, 1 before the fix landed, 3 again after. ## 0.68.3 — 2026-09-19 08:59 PDT **One-tap commands, declared per CLI, not coded per CLI.** A row above the prompt box for text a CLI is asked for often: Claude Code gets `/clear` and `/compact`. A `quick_commands` list in `clis.toml`, next to `modes`, is the whole feature. Add a CLI, list its commands, reload, done, same as adding a mode. A tap sends the command through the same path the prompt box's own Run button uses, so the destructive-command check still applies. No row shows for a CLI that lists none. Android is unchanged, since it does not yet read the mode pill either; both are the same follow-up. ## 0.68.2 — 2026-09-19 08:00 PDT **A phone that is switched off no longer holds the desktop at phone size.** Closing the Android app does not always close its connection: the app is killed, or the network goes, and the panel is left holding a socket nobody is behind. The thread reading it parked forever, and behind that thread sat a terminal and a tmux viewer that still counted as attached, so the reaper skipped it. Every view of a session shares one window size, so a phone that had been off for thirteen hours was still pinning a 53x47 pane onto the desktop. The panel already pinged every 25 seconds without ever noticing that nothing came back. Two unanswered pings now end the connection, which releases the terminal and the viewer with it. A connection that is answering is untouched, and a browser or the Android app both answer without being asked. ## 0.68.1 — 2026-09-18 18:41 PDT **The desktop client's own version, next to the panel's.** The version in the corner is the server's, and in the desktop client that is not the version of the thing on screen. It had shipped eleven releases with nowhere at all to read which one was running. A shell that wraps the panel now announces itself before any page script, and the footer and the About tab print it beside the panel version: `v0.68.1 · desktop 0.3.12`. A browser announces nothing and nothing is drawn, which is the same page it was before. ## 0.68.0 — 2026-09-18 17:42 PDT **Sign in on a phone by scanning a QR code.** Pairing already minted a short code. The desktop now draws a QR of a sign-in URL next to it, and opening that URL on the phone signs the browser in without typing the panel password. The Android claim path is unchanged. A QR of localhost cannot work from a phone, so one is not drawn until the panel's public URL is set in Settings. ## 0.67.13 — 2026-09-18 16:52 PDT **A peek no longer comes back reading `❯`.** Peeking at a session, and the "saying" line under every row in the sidebar, both rest on dropping the frame a CLI draws: a line is not worth quoting when every character in it is a box rule, a separator or a prompt mark. Whitespace was a list of two characters, a space and a tab, and Claude Code ends its bare prompt row with a non-breaking space. That one invisible character made the row content rather than frame, so it survived the filter, and then `rstrip` took it off again and left a lone prompt glyph as the last thing the session said. A rule padded the same way got through beside it. Whitespace is now asked of the character rather than listed, which covers every kind of space a CLI can pad with instead of the two that were guessed. Found on the phone, where the new peek control put four lines of frame under a session row and made it obvious. ## 0.67.12 — 2026-09-18 14:24 PDT **The bottom line of the terminal is no longer half cut off.** It looked like the input bar was sitting on top of the pane. It was not: the terminal was drawing one row more than it had room for, and that row was clipped in the middle by the pane's own edge. The fitting addon measures the terminal's parent, and the pane's padding is not on that element, so it offered a row that did not fit. Measured on a 737 pixel pane: 49 rows proposed against 48 rows of room. The extra row is now handed back before the terminal is resized, and the trimmed count is compared against the rows already on screen, so nothing resizes twice or oscillates. Most visible with a CLI that draws its own prompt, where our prompt box is hidden and the bar is a single pill, because that is when the pane is tallest. ## 0.67.11 — 2026-09-18 12:18 PDT **A web address without the `https://` in front of it is now a link.** Our own output prints them that way, and clicking `fdroid.useclique.dev/repo` did nothing at all. The pane now underlines a host with a path and opens it over https, wrapped across two rows or not. The slash is required, and that is the whole of the defence against filenames: `.md`, `.sh`, `.pl`, `.zip` and `.mov` are all real top-level domains, so a bare dotted word can never be enough to tell a domain from `README.md`. A dotted directory inside a path, an email address, and the tail of a real `https://` URL are all still left alone. ## 0.67.10 — 2026-09-18 11:26 PDT **A Markdown file opens as a document, not as a wall of hashes and pipes.** Agents write Markdown constantly, and until now clicking one got you its source. Headings, lists, tables, fenced code, quotes and links now render in the file sheet, with a **Source** button to see the raw file and Edit still editing the raw text. A truncated file renders what arrived and still says so. Images referenced from a document load when they sit beside it, which is the case worth having: a note that points at the screenshot next to it now shows it. The renderer builds the page element by element rather than handing HTML to the browser, because a working directory is not a trusted place: a cloned repository can contain a file written to attack whoever opens it. Nothing from a file is ever parsed as HTML, and a link that is not `http`, `https` or `mailto` renders as plain text rather than something clickable. ## 0.67.9 — 2026-09-18 10:19 PDT **A long path the pane printed is clickable again.** Paths were matched one terminal row at a time while URLs were matched across the whole wrapped line. Anything long enough to wrap was therefore split between two rows, and neither half matches on its own, so it quietly stopped being a link. The paths this hit were the long ones, which are exactly the ones worth clicking rather than retyping: a pasted screenshot's path is about seventy characters and wraps on a phone, in a narrow pane, or in a pane a phone has just resized. Paths now read across the wrapped line the same way URLs already did, and light up on every row they cross. ## 0.67.8 — 2026-09-17 11:51 PDT **A phone no longer leaves your desktop panel in a screen of dots.** A tmux window has one size, shared by every client attached to it, so a phone opening a session shrinks the panel looking at the same one. A handheld is allowed to win that, deliberately: a phone is picked up to do the thing that could not wait. What was missing is the other half. Nothing put the window back when the phone let go, and a desktop panel only takes its size back on the poll if it has been touched in the last 45 seconds, so one sitting there untouched showed a 53 column pane inside a 235 column window until somebody clicked it. The server now remembers the last size a desktop asked for, whether or not it was allowed to move the window at the time, and restores that on release. A read-only viewer is excluded, because a viewer does not size what other people are looking at. The rule that stops two desktop panels resizing each other every three seconds is untouched, and a phone that dies without saying so is still the 90 second backstop. ## 0.67.7 — 2026-09-17 09:09 PDT **Pair a device instead of typing a token into it.** An API token is forty-odd characters, which is a copy and paste on a laptop and a transcription error on a phone. The panel now hands out a short code, in two groups of four, with none of the characters people mistype. A device trades the code for a real token named after itself, so it can be recognised and revoked later like any other. The code is single use, expires in two minutes, and there is never more than one outstanding. Minting one still requires being inside the panel; only redeeming it is open, because the device doing that has no credential yet. Five wrong guesses burn the code, and every refusal reads the same whatever the reason, because saying which tells a guesser where they are. This is the groundwork for native clients. The plan, the order and what is refused are in `ROADMAP.md`. ## 0.67.6 — 2026-09-17 07:56 PDT **The Android keyboard stopped pasting back what you had just sent.** Accept a spelling suggestion, send, and from then on everything typed came back with what was already sent attached, over and over, until the app was restarted. The box was emptied by assigning to it, and an Android keyboard holds its own copy of the field while it is offering a correction, kept as positions rather than text. Assigning underneath that leaves the two out of step, and every later keystroke is applied against where the old words used to be. It now ends what the keyboard has in flight before emptying the box, which resyncs them, and takes the text out once more if it comes back anyway. **A refused host now says which host.** "host not allowed" named neither the value nor the setting that fixes it, which matters because this panel is built to sit behind a proxy and a proxy is exactly what gets that wrong. ## 0.67.5 — 2026-09-16 19:03 PDT **The installed app now says when it cannot reach the panel.** It used to show nothing at all: a blank window with no address bar, no reload button and no message, so a panel that was merely unreachable looked exactly like an app that was broken. It now shows a page that says the panel did not answer and offers a way back in, and it reloads itself when the network returns, so a dropped VPN or a walk out of wifi range does not need anyone to notice and reopen it. ## 0.67.4 — 2026-09-14 08:56 PDT **The bottom of the app stopped jittering with the keyboard up.** The keyboard handler pins the page back to the top, and it was also listening for the visual viewport's scroll event, so the pin was itself a scroll that re-ran the handler. iOS scrolls the focused input into view, this scrolled it back, and the two fought. It now does nothing unless the visible height has actually changed. ## 0.67.3 — 2026-09-05 19:38 PDT **Two follow-up fixes to the phone keyboard handling.** In the installed app the input bar could still slip behind the keyboard, because the keyboard-aware height did not account for the safe-area padding; it now does. And a pinch-zoom, which also shrinks the viewport, was being mistaken for the keyboard; it no longer is. ## 0.67.2 — 2026-09-05 19:07 PDT **The keyboard no longer covers the prompt, and the header stays put.** On a phone, opening the keyboard now shrinks the app to the space above it, so the text field sits above the keyboard and the top of the screen no longer scrolls off. It uses the browser's own keyboard-resize hint where that is supported, and tracks the visible viewport everywhere else. ## 0.67.1 — 2026-09-05 17:39 PDT **Two more phone fixes.** Dragging to scroll the terminal in the installed app no longer starts a text selection and the copy menu, so a scroll stays a scroll. And the safe-area padding no longer pushes the bottom of the screen off the display, so the input bar and the last of the scrollback are visible again on a phone. ## 0.67.0 — 2026-09-02 18:51 PDT **The three things a phone still could not do.** All from the review a second model wrote on 2026-09-02, and all confirmed against the code first. **The long-press menu scrolls.** A live session's menu is fifteen to twenty rows, and at the padding a thumb needs that is 600 to 800px, taller than a phone. The placement clamp goes negative when the menu is taller than the window, so Open was cut off above the screen while Kill sat below it, and there was nothing to scroll. The menu whose whole reason for existing is that a phone can archive, lock and kill from it could do none of the three. **A working group answers a long press.** Rename and delete were right-click only, so from a phone you could launch a group and never change or remove one. The sessions and the groups now share one implementation rather than two. **You can hand a session a file, and get text back off the pane.** Drop needs a mouse and paste needs the file already on the clipboard, so between them a phone had no route in at all: no photo, no PDF, no log from another app. There is a paperclip next to Run now, and an entry in both menus. Coming the other way, a finger drag is our scroll and nothing else makes a selection, so the Copy chip never appeared; long-pressing the output offers the line you pressed, what is on screen, and the last twenty lines. **And one the paperclip uncovered.** Every control in the input bar refuses to shrink and the textarea was the only thing giving, so on a 320px phone the prompt measured 26 pixels — narrower than one word, on the box you type every prompt into. It went unnoticed while a phone typed into the terminal instead, and 0.66.0 made that box the way a phone talks to an agent. Under 460px the bar wraps and the box takes the row. ## 0.66.1 — 2026-09-02 15:46 PDT **The other half of the Android fix.** 0.66.0 gave a phone the panel's own prompt box and then handed the keyboard to the terminal anyway: opening a session focused the pane, and tapping Esc or Ctrl-C on the key row took focus back. So the box was there, and Gboard was still typing into the terminal, and still duplicating the line. On a touch device showing the box, nothing takes focus on its own now, and the first thing you tap decides. Escape out of the prompt box still hands the pane the keyboard, because that is somebody asking. **The installed app no longer draws under the notch.** The standalone rule that pads for the safe area has been in the stylesheet for a while and was doing nothing, because `env(safe-area-inset-*)` reports 0 without `viewport-fit=cover` in the viewport tag, and only the sign-in page had it. The panel has it now, the shell measures the body rather than the whole viewport so the padding is not undone one level down, and the toast clears the home indicator, which is where Undo after a kill lives. Both found by having Grok CLI read the mobile surface and say what it thought, which is the first time a second model has been pointed at this. Its write-up is in `docs/mobile-review-grok.md`; the three findings still open are on `docs/next.md`. Every one was checked against the code before being believed. ## 0.66.0 — 2026-09-02 15:29 PDT **Typing on an Android phone no longer repeats the line you just sent.** With a suggestion from the keyboard, or a paste, the same sentence would arrive in the pane over and over. It made the phone close to unusable, which was the whole point of the last six releases. The cause is not really ours to fix. A CLI that draws its own prompt box gets the terminal to itself, so on a phone you were typing straight into the terminal, one keystroke at a time, through the keyboard's input method. Gboard predicts, autocorrects and pastes into a hidden field the terminal keeps emptying under it, loses track of what it believes is there, and re-commits the whole line. It is worst with a suggestion or a paste, which is exactly when the keyboard has the most to re-commit. So a touch device now always gets CLIque's own prompt box. Composing in an ordinary textarea and sending the finished line never goes near that path. The cost is the CLI's own box sitting above ours, which is one bar of screen against a phone you cannot type on. Setting the prompt-box mode to **Terminal** still overrides it. Two more things found on the way there, and they were live on a desktop too. An Enter arriving mid-word from an input method is no longer treated as send, and `run` no longer accepts a second Enter while the first is still waiting on a confirm or a request. Both sent the same text twice. **A different theme on a schedule.** Tick the themes you like under **Appearance ▸ Change it on a schedule** and CLIque puts a different one on every so often. Every 24 hours from 07:00 is a fresh theme each morning; every 6 hours from 07:00 changes at 07:00, 13:00, 19:00 and 01:00. It picks at random from the ones you ticked and never the one already on, so a change always looks like one. **Change now** does it on the spot. It rides the panel's own poll rather than a timer, so nothing happens while nobody has CLIque open, and opening it after a few days catches up to the last change that was due rather than running through every one it missed. The server still has no idea what a theme is: the presets live in `web/themes.js` and it only moves an id you chose. `POST /api/themes/rotate`, five settings, and a check in `tools/rotate_check.py`. **Also:** the visual suite's boxed-pane click test had been failing since 0.62.0, aimed at the wrong pane and at a session record the panel had not loaded yet. Fixed, so the check that a click reaches the CLI is doing its job again. ## 0.65.0 — 2026-09-01 12:58 PDT **A session whose saved conversation is gone now starts anyway.** Clicking one did nothing at all: no pane, no error, nothing to click again. It had been doing that for a week before anyone worked out why. A resume key can outlive the thing it points at. Open a session, never type in it, let the idle reaper stop it six hours later, and the CLI has nothing on disk to come back to. CLIque asks for it anyway, the CLI refuses in about a second and a half, and the tab goes quiet. The panel now watches a resume for as long as eight seconds and, the moment the pane turns out to be gone, drops the key and starts the session clean. Usually over in two seconds, and there is nothing to click, learn or turn on. It only does this for a key that is CLIque's own session id, which is the one case where we know how it got there and can derive it again; a key that came from anywhere else was typed deliberately and is left alone. Stopping a session inside that window wins, so nothing is ever resurrected behind you. Nothing is read but process state: the pane is there or it is not. Working out *why* would mean reading the CLI's output, which is the line the core does not cross. `POST /api/sessions//start` now answers with `resumed`, saying whether the resume form was used. New check: `tools/resume_check.py`. ## 0.64.0 — 2026-08-30 14:56 PDT **Tapping the prompt on an iPhone no longer zooms the whole page.** Safari zooms when it focuses an input whose text is under 16px, and the prompt box was 13px, so the single most common thing you do on a phone moved the layout under your thumb every time. Every input, textarea and select is at least 16px on a touch device now, and untouched on a desktop, where the font size you chose is the font size you get. That rule carries `!important`, which is the case the keyword exists for: it is an override of an operating system behaviour rather than styling, and it has to beat every component that sets a font size without any of them knowing about it. Written politely it did nothing at all, because `.search input` and the settings fields simply won. **The status bar stopped hanging off the screen.** 460px of controls in a 393px phone, which is what the clipped icons actually were. The readings already dropped out in order of how much they decide; the controls on the right never did. They go the same way now, and the two that go first are the two a phone cannot use anyway: a keyboard-shortcut sheet and a font stepper that Settings also has. **Tap targets: 34 under 44px, now 9.** Growing a 17px tab close button to 44 would wreck a 35px strip, so the *hit area* grows instead and nothing moves by a pixel. Nothing changes on a mouse, where 17px is a perfectly good target. The nine that remain are the key row, at a correct 44px tall and as wide as seven keys can be on a phone, which is a decision rather than a defect. **Tetris is now Bricks and Pacman is now Chompy.** The artwork was never the issue: every figure is an original design. The names were, and both were actively enforced marks. The id is what gets stored, so a bare rename would have dropped anyone wearing one back to the default and looked like the panel forgot; the two old ids are aliased to the new ones, in the theme lookup so it still applies and in the picker so the dropdown agrees rather than reading empty over a theme that is plainly on. **`agent-infra/tools/mobile_view.py` stopped crying wolf twice**, which matters because a check nobody believes is worse than no check. It measures the *hit* area now, including one grown by a pseudo-element, so a fixed control stops being reported as broken. And it ignores elements clipped by an ancestor: a terminal pane for a background session is thousands of pixels wide inside `overflow: hidden` and was being reported as "the page scrolls sideways" when the page plainly does not. ## 0.63.0 — 2026-08-30 14:40 PDT **Working groups: sessions you always open together.** A group holds a Claude, a Gemini and a Grok, or three of the same, from one directory or three unrelated ones. One click opens all of them, side by side, under a band that says they belong together. **Not folders**, and the difference is the whole feature. A folder files a session in the sidebar and a session has exactly one. A group is about launching several things at once and telling at a glance which tabs belong to which piece of work, so it can pull from several folders or none, a session can sit in more than one, and joining one changes nothing about where a session lives. **The band is inside the tab strip, not above it.** Chrome hangs a labelled band over its tab groups and spends a row of height on it. There is no row to spare: the strip is 35px and on a phone it is competing with the pane for the only screen there is. So it is a rule along the bottom edge of the tabs, which costs nothing, needs no phone-specific layout, and is the same on both. The group's *name* lives in the sidebar, where there is room for words and where you launch it from anyway. Opening a group gathers its tabs into one run. That is not decoration: the same colour scattered through the strip reads as three unrelated pills rather than one band, which is exactly what the first version drew. Tabs already open are moved rather than duplicated, so opening a group twice tidies the strip instead of growing it, and the block lands where the first member already was rather than throwing you to the end of a strip you were working in. **A member is a snapshot, not a pointer.** Each one stores the session's CLI, directory and name beside its id, so a member whose session has been deleted is reported rather than quietly missing. It is not recreated unless you ask, since a group silently starting something you deleted on purpose is the worse failure, and recreating rewrites the member so the next open does not strand it again. `POST /api/groups//open` is the whole thing, so a script can open a working group too. It reports what was already running, what it started, what is missing and what refused, separately rather than as a count. ## 0.62.1 — 2026-08-30 12:40 PDT **A reload button, for the installed app that has nowhere else to get one.** A PWA has no address bar, so a panel that ever needs a fresh load leaves you closing it and reopening it from the home screen. It sits in the status bar, which is outside the sidebar and therefore still there when the drawer is shut on a phone. Shown only where it is the only way in: the installed app, full screen, and the iOS home screen, which has its own flag because it predates the standard. A browser tab does not get one, because it would be a button doing what the browser's own reload already does, in a row that is already full. The command palette offers it everywhere either way, which is also the answer for a keyboard that cannot reach the browser's reload because the pane has the keys. The service worker is asked to update before the page reloads. It does not cache, so a plain reload already fetches fresh code, but the worker is the one thing a reload would otherwise keep, and "reload" meaning "all of it except the part that serves you" is a promise not worth making. Sessions keep running, and the tabs and layout come back, because the workspace has always lived on the server rather than in the tab. ## 0.62.0 — 2026-08-30 08:56 PDT **The active CLI's logo, faint in the top-right of the pane.** Opposite corner to the theme character, so a theme with one and a session with the other never sit on top of each other. The pane edge has carried the CLI's colour for a while, and that answers "which one am I in" for anyone who has learned the colours. This answers it for everyone else, and answers it across a screen of panes rather than by reading a tab. At seven percent it is a thing you notice when you look for it and not when you are reading output. Two shapes of icon, the same split the sidebar markers already make. A single-colour glyph becomes a mask tinted with the CLI's own colour, which is what lets one file serve the tinted, grey and watermark modes at once. A logo that carries its own colours cannot be a mask, because it flattens to a solid square, so it is drawn as the image it is. A CLI with no icon draws nothing rather than inventing a letter: a letter blown up to a hundred pixels in a corner reads as a mistake, not as decoration. Desktop only. It takes itself away on a pane under 720px and a window under 460px tall, on the same rule the theme character uses, because on a phone the corner is needed for output. One checkbox under **CLI markers** turns it off. ## 0.61.0 — 2026-08-30 08:26 PDT **Scrolling the terminal with a finger works.** It never had, and there were three separate reasons, each of which had to be found before the next one was even visible. **tmux was keeping no scrollback at all.** Every agent CLI enters the alternate screen at startup, and an alternate-screen pane keeps no history by design: measured `history_size=0` against a 20000 limit on every live session. So the history this panel sends a reattaching browser had nothing in it, and a phone opening a session got exactly one screenful with nothing above it. tmux is now told to ignore the switch, which puts the output in the normal buffer where it scrolls into history: measured 279 lines against 0 for the identical program. **The browser was showing the wrong buffer.** With history finally arriving, a browser still had none, because the alternate-screen switch was stripped only for CLIs that own their input. Everything else parked xterm in the alternate buffer, which holds one screen and no scrollback by definition. Measured: 360 lines sitting in `buffer.normal` while the pane displayed the 45-line alternate one. Every session hides that switch now. **And nothing was listening for a finger.** Scrolling the pane has always been this panel's own wheel handler rather than the browser's, because xterm needs telling which of the two buffers a scroll belongs to. There was no touch equivalent, so on a phone the pane did not scroll: not a broken gesture, an absent one. A drag now scrolls it, using the same branch the wheel uses, and `touch-action: none` on the pane is what lets the gesture reach it at all. Without that the browser claims a vertical drag for the native scroll of a scroll area that is exactly one screen tall, and swallows it to move something that cannot move. Measured before: one touchstart, zero touchmoves. Pixels are carried between moves rather than rounded away, so a slow drag tracks your thumb instead of doing nothing until you flick, and a short movement is left alone so a tap still reaches the CLI. The trade, stated plainly: a full-screen program that exits no longer has its screen restored, so `vim` in a shell session leaves its last frame behind. For a panel whose whole purpose is reading what an agent did, keeping the transcript is the better half of that bargain. `tools/visual_check.py` drives a real touch drag and asserts all three: that the pane shows the buffer history is in, that there is history in it, and that a drag moves through it and back. ## 0.60.0 — 2026-08-30 08:05 PDT **CLIque installs as an app on a phone, from the page you actually arrive on.** The panel had a correct manifest, a service worker and every icon size for years. The sign-in page had none of it, and the sign-in page is what a phone meets first, so "Add to Home Screen" gave you a bookmark with a screenshot for an icon and Safari's chrome around the result. **The cause was an allow-list that had quietly gone dead.** `PUBLIC_ASSETS` names what may be served before login, and a past fix for a real traversal hole (`/brand/../app.js` resolved back to the app shell) replaced the whole check with a single "is it inside `brand/`" test. That closed the hole and, with it, silently un-published the manifest and the favicon: the constant was still declared, still read like documentation, and referenced by nothing. A browser cannot offer to install an app whose manifest it is not allowed to read. The allow-list is honoured again, and the traversal stays closed, because the comparison is between *resolved* paths rather than prefixes. `/brand/../app.js` resolves to the shell and matches nothing; `/brand/../manifest.webmanifest` resolves to a file that is public anyway. `sw.js` joins the list, because Chrome judges installability from the page in front of it and will not offer to install one with no worker registered. **Three things fixed on the way in, all on the sign-in page**, all found by `agent-infra/tools/mobile_view.py` rather than by looking: - The password field was 14px, so **iOS zoomed the entire page the moment you tapped it**. The first thing anyone does on a phone, and it moved the layout under them. 16px is the threshold and is not a taste decision. - The field and the button were 42px tall against a 44px thumb. - At 320px the page **scrolled sideways**, because a 100% width plus padding and a border comes to 323px without `box-sizing: border-box`. Every device the tool renders now reports nothing on that page. The app's own name lost an em dash. It is the text on your home screen. `tools/smoke_http.py` now asserts both halves of the allow-list from a logged-out client: that the manifest, favicon, worker and brand icons are served, that the shell, its script, its stylesheet and its themes are not, and that four traversal attempts through `brand/..` reach nothing. That is the test whose absence let the list go dead. ## 0.59.0 — 2026-08-29 16:41 PDT **Open what a CLI hands you, without leaving the panel.** Three things that were nearly there and did not join up. **The file sheet was broken and had been for a long time.** It opened, showed the name and the path, and never showed the contents. One of the two assertions that had been failing in `tools/visual_check.py` was saying exactly that, and it had been read as background noise. It shows the text now. **Select a path in the pane and an Open button appears** beside the Copy chip that was already there. Not a hover link: there is no hover on a phone and no right-click either, and the selection chip is a control you can already reach with a thumb. A sentence is not a path, so selecting prose offers nothing. **An image renders in the sheet** instead of being described, and **a directory lists what is in it** and lets you click through. Nothing about what can be read has changed, which was the part to get right. Listings hand back the child as it is named in that folder rather than a resolved target, because a resolved path is how a symlink leaks somewhere it should not; `..` appears only when the parent is still inside the session's directory; and every click still goes through the same resolve and fence as before. Verified by trying: `../../../../etc/passwd`, `/etc/shadow`, `..`, `/root/.clique/secret` and four more all come back as `missing`, and the suite now asserts that a session folder's own listing offers no way up. Most of this was written by Grok running inside CLIque, which is the first time the panel has been used to build itself. It solved the third piece better than it was briefed: asked for clickable paths in the terminal, it used the existing selection chip instead, which needs neither hover nor right-click and was already the phone-safe control. ## 0.58.0 — 2026-08-29 16:11 PDT **A phone and a desktop no longer fight over the terminal, and the phone wins.** With a desktop panel open on one machine and CLIque open on a phone, both attached to the same session, the pane resized itself every three seconds forever. The CLI reflowed to 162 columns, then to 42, then back. On the phone that made it close to unusable. A tmux window has one size and every attached client sees it, so two panels of different shapes cannot both be right. The guard was `document.hasFocus()`, which cannot decide this: it is per browser window, and a desktop on one machine and a phone in your hand **both report true, because both are true**. So each one saw the window at the other's size on every poll, concluded it had been resized out from under it, and claimed it back. The server settles it now, because it is the only party that can see both clients. **A handheld wins.** That is a decision about how the thing is used rather than a heuristic: a phone gets picked up to do the thing that could not wait, while a desktop panel is often just open. A desktop's resize still sizes its own PTY, so it keeps drawing at its own shape and says so in the pane's size label, but it does not move the window underneath the phone. A phone in a pocket does not hold anything: a dark screen or a backgrounded tab sends `release` and the desktop has the window back on its next poll, with no timer to wait out. A phone that vanishes without saying so, a killed tab or a dead battery, expires after 90 seconds. While it is awake it sends a `hold` on each poll, which is deliberately cheap - no tmux call, no resize - and exists because a phone that already owns the window has nothing left to resize and would otherwise let its own claim lapse. Measured rather than reasoned about, with two real browser contexts attached to one session: 5 resize messages in 12 idle seconds before, 0 after, and the window holds at the phone's size through a desktop being actively clicked. **Found with the new `mobile_view.py`**, which renders iOS in WebKit and Android in Chromium at real device sizes. Three things it turned up that are not fixed here and are now on the list: 34 inputs under 16px, including the prompt box and the search field, which make iOS zoom the whole page whenever they are tapped; 29 tap targets under 44px; and a login page that scrolls sideways at 320px. ## 0.57.0 — 2026-08-29 10:32 PDT **Open the same work in another CLI.** Right-click a session and pick *Open in another CLI*: same name, same directory, same folder, a different tool. The two sit side by side as tabs reading the same thing, told apart by the CLI marker rather than by a suffix nobody asked for. Handing Codex the job Claude has been chewing on should not mean retyping a path. It only appears when another CLI is actually installed, because an item that can only disappoint is worse than no item. The mode is deliberately not carried across: a mode is something a CLI declares in `clis.toml`, so the source's mode id means nothing to the target and would either be rejected or, worse, quietly match something unrelated. The new session takes the target's own default. *Duplicate* is now labelled **Duplicate (same CLI, same directory)**. It used to read "same directory, fresh CLI", which sounds exactly like the feature above and is the opposite of what it does. **Two menu items had been dead for twelve releases.** *Move to folder…* and *Take out of its folder* both threw a `ReferenceError` and did nothing: 0.44.0 deleted `setFolder` and `moveToFolder` and left the two call sites behind. Both are restored. Nothing caught it, and the reason is worth writing down. `node --check` parses `app.js` happily with a call to a function nobody wrote, because the reference is only resolved when somebody clicks; and a throw inside a click handler goes to a console nobody has open. So the failure mode is a menu item that looks completely normal and silently does nothing. `tools/smoke.py` now reads every arrow-wrapped call in `app.js` and fails on one that resolves to no declaration. It found `setFolder` immediately, which is the second of the two. `tools/visual_check.py` goes further and actually right-clicks a session, opens both submenus and asserts nothing threw, because "the function exists" and "the menu works" are different claims and only the second one is the feature. ## 0.56.0 — 2026-08-29 08:26 PDT **Type a project name in the new-session dialog instead of a path.** "sentinel" now finds `/root/platform/wsg-sentinel` and offers it. The same box still takes a path and still completes one; a bare name used to return nothing at all. That gap was the point. The dialog had two ways to fill in a directory and both assume you already know something: the dropdown lists everywhere you have worked, which is no help for a repo you have not opened here yet, and the completion behaves like a shell, which needs the first few characters of the path. Neither answers "the one called sentinel", which on a box with forty repos across three parent directories is the actual question. A directory counts as a project when it holds `.git`, `pyproject.toml`, `package.json`, `Cargo.toml`, `go.mod`, `pom.xml`, `Gemfile`, `composer.json` or `CMakeLists.txt`. Matches are ranked by how the match happened rather than by string distance, because the directory *called* sentinel should beat one that merely has the word somewhere in its path, and no fuzzy score says that as clearly as asking the question in order. Somewhere you have already worked stays ahead of a fresh find either way. **The walk is cheap because it never opens a hidden directory.** That one rule removes the caches, the virtualenvs, the package directories and the language toolchains, which is most of the bytes in a developer's home directory: it takes an 11GB `~/.cache` out of the walk entirely. 140 projects in about a tenth of a second here, cached for two minutes after that. It also keeps going *past* a project root, which is the opposite of the obvious optimisation. Stopping there was measured against not stopping and came out inside the noise, while hiding real answers: a repo of client directories that is itself a repo is an ordinary thing to have, and every directory inside one was invisible. It was a trade that bought nothing, and it only stopped looking clever once somebody measured it. Bounded three ways, by depth, count and a three-second budget, and a walk that hits any of them says so in `partial` rather than returning a quietly short answer. **`GET /api/projects?q=`** is the whole thing, so an agent driving CLIque can ask where a repo is without being told. **`project_roots`** in Settings ▸ Appearance narrows where it looks; blank means your home directory, which is the answer that is right on a machine nobody has configured. ## 0.55.1 — 2026-08-29 08:16 PDT **The theme picker says which themes come with a character.** Seven of the sixteen do and it was not guessable from the name, so the list is grouped: **Presets**, then **With a character**, then **Made here** once you have generated one. A group rather than a marker beside the name, because a marker needs a legend somewhere saying what it means, and this says it in words. It is also a native control, so it survives a phone and a screen reader and hides behind no hover or right-click, which a small glyph in a corner would have done. Nothing moves. `themes.js` already declares the plain presets, then the ones with a figure, and anything made here is merged onto the end, so the groups fall exactly on boundaries that were already there and the order in the list is unchanged. The command palette says it too, since that is the faster way to switch: a theme with a figure reads `dark · has a character`. ## 0.55.0 — 2026-08-28 21:26 PDT **Seven themes with somebody in them.** Plumber, Triforce, Fellowship, Drizzt, Pacman, Tetris and Aincrad, each a full palette plus a hand-drawn anime figure watermarked into the bottom-right of the pane. Sixteen presets now, up from nine. **They are original designs, not the famous characters, and that took deciding.** Asked plainly for a moustachioed plumber in a red cap and blue dungarees, an image model returns Nintendo's Mario line for line: same face, same moustache, same proportions. That is not inspiration, it is the character, and this package is published on PyPI under a real name. The archetype is fair to borrow and the likeness is not, so every prompt carries an explicit instruction to invent the face, the costume and the proportions fresh. Anyone regenerating these has to keep that, which is why it is written above the character block in `themes.js` rather than left in somebody's memory. **The figure is laid in faintly rather than blended.** An earlier pass composited with `lighten` on dark themes and `darken` on light ones, which keeps text perfectly untouched, but only works because the colours in a hand-built grid are all mid-tones by construction. A real drawing has blacks and whites in it and those blends eat precisely those. So artwork goes on at twelve percent, normally composited, which costs the text a little contrast where the two overlap. That is the honest trade for taking art as it comes instead of dictating a palette to whoever drew it. It takes itself away on a pane under 720px and a window under 460px tall, so a phone and a pane squeezed between the sidebar and the side panel both get their corner back. That is asked of the **pane**, not the window: with both panels out a 1600px window is a 700px pane, and a window query would be wrong by exactly the width of the sidebar. Same mistake the status bar's readings made once. One checkbox under the theme picker turns them all off. **`tools/art_prep.py`** turns an illustration into a shippable figure: keys out the flat background it was drawn on, unmixes that colour from the antialiased edges so nothing ships wearing a pink rim, crops to the figure, scales it and quantises it. A megabyte of magenta field becomes about a hundred kilobytes of exactly the character. Cel-shaded art is the case a palette handles without showing, which is what keeps seven drawings under a megabyte in a package whose whole argument is that it is small. `tools/theme_check.py` now checks the shipped files rather than a grid: that each one is there, is a PNG, is in a colour type that can actually hold transparency, is within its size and dimension budget, and that `pyproject.toml` is told to package the directory. That last one is not hypothetical. `package-data` is an allow-list of globs, so a new directory under `web/` is silently left out and the panel serves 404s on somebody else's machine while working perfectly here. `tools/visual_check.py` opens a pane with a figure in it and measures the compositing, the opacity, that it cannot be clicked, that it stays inside the pane, and that it disappears and comes back with the width. None of that is visible to a check that reasons about the code, which is the whole reason it is in the browser suite. ## 0.54.1 — 2026-08-28 18:17 PDT **Plan usage is a pair of meters now, not another number in the machine stats.** It had been sitting between disk free and uptime reading `PLAN 5H 5%`, which made it look like one more thing the box is doing. It is not: how much of the week is gone decides what you start next, and CPU does not. It has its own block with a hairline either side, a short bar each, green until three quarters, amber past that and red past ninety. The exact figure stays beside it, and the reset countdown is still on hover. **Nothing in the status bar is cut off mid-word any more.** The meters cost the row about two hundred pixels it did not have, and the first attempt left VIEWS reading "VIEW". Readings now drop out whole as the row narrows, in reverse order of how much they decide: views first, then load, then disk. On a phone the meters keep their bars and drop the digits, which is the half you read at a glance. That is asked of the row rather than the window. A window query gets it wrong by exactly the width of the sidebar, which is why the first attempt fired at the wrong moment: 1280 pixels of window is barely a thousand of status bar with the sidebar open, and collapsing it has to change the answer. `tools/visual_check.py` now measures every reading against the ends of the bar at two window widths and with the sidebar both ways, so nothing can quietly start hanging off the edge again. ## 0.54.0 — 2026-08-28 18:07 PDT **How much of the plan is left, in the status bar.** For the session in front, the bar now reads something like `5H 5% · 7D 52%`, amber past three quarters and red past ninety. Hovering says when each window resets, in the form that actually decides whether to keep going: "resets in 2h", not a timestamp. **Nothing in the panel knows whose API that is.** A CLI's `usage` block in `clis.toml` says where its token file lives, which field in it holds the token, which URL answers, and which fields in the reply are the numbers. The panel runs that description and learns nothing else. Teaching it about another vendor is a block of TOML rather than a line of Python, which is the same bargain `clis.toml` already makes for launching a CLI at all. Delete the block and the column goes away. **It works on a machine that has never heard of us.** This was the whole point: the probe ships in the package and reads the credentials the CLI already wrote, so someone who installs `clique-panel` and opens it gets the bar. Nothing here depends on a cron job, a file another tool writes, or a key in somebody's vault. **The token is spent, not spread around.** It is read from disk, used on one request, and dropped. What reaches the browser is a percentage and a reset time, and never the credential. Only CLIs that declare a probe *and* have a session open are asked, so a fresh panel with nothing running makes no outbound call at all. Readings are cached five minutes and shared by every connected browser; failures are cached the same, so a machine with no credentials asks once and then stops. Everything that can go wrong goes quiet rather than loud: no token, an expired one, no network, a reply in a shape the block did not describe. `usage_bar` in settings turns the whole thing off. Today Claude is the only CLI shipping with a probe, because it is the only one of the four installed here whose vendor publishes a usage endpoint. Codex and Grok keep credentials on disk but document no such endpoint, and none of them cache the numbers locally. The slot is there for the day that changes. `tools/usage_check.py` points the probe at a throwaway local server that is nobody's real API, which is the strongest evidence the code is generic, and covers every quiet failure. It also asserts the shipped catalogue still says what it said: a sub-table opened in the wrong place silently swallowed four of Claude's keys while this was being written, and that class of mistake now has a guard. ## 0.53.0 — 2026-08-28 17:35 PDT **Describe a theme and get one.** Settings, Appearance now has a box: type "a quiet winter morning, muted blues", press Generate, and the theme is built, applied, and kept on the server so it follows you to any device. Each one can be worn or deleted from the same list. It runs on your own key through the existing model providers, so nothing is called unless you set one up, and the button says so plainly until you have. **The model is asked for nine colours, not twenty-seven.** A theme is nine panel tokens and eighteen terminal ones, all sixteen ANSI colours with their brights included. Ask for that and you get a missing `brightYellow` and a foreground nobody can read. So it supplies the ones that need taste, light or dark, a background, a foreground, an accent and six hues, and the rest are worked out, which is the same bargain the panel already makes with its own derived tokens. **A generated theme cannot lock you out.** This is the part that mattered. The settings sheet you would use to pick a different theme is drawn in the theme you are wearing, so one whose text vanishes into its background is not a bad theme, it is a panel you cannot navigate. Every colour that carries meaning is pushed away from the background until it can be read, before the theme is ever stored: body text to 7:1, secondary text to 4.5, every ANSI colour to 3.5. Colours are corrected rather than refused, so a strange description still gives you something usable. **A theme can also be posted by hand.** `POST /api/themes` takes the same nine colours and runs the same derivation and the same contrast pass, so anything driving CLIque from a script can make one without a model anywhere near it. Forty are kept; the oldest goes when a new one would exceed that. Deleting the one you are wearing falls back to the default rather than leaving the setting pointing at nothing. `tools/theme_check.py` covers the lot over HTTP: derivation, every refusal, the contrast rescue, wearing, deleting and the ceiling. ## 0.52.1 — 2026-08-28 17:12 PDT **The pane's scrollbar wears the theme.** It was the last raw browser control left in the panel, sitting against the terminal all day in whatever grey the browser felt like. It is a thin themed one now, and it changes when the theme does. The inset is a transparent border rather than the panel colour the settings sheet uses, because a terminal's background is the theme's *terminal* background, which is not always the panel's. **Three terminal colours a theme should never have had to spell out.** They are worked out from what the theme already said, the same bargain the panel's own tokens make, so a theme stays one block and cannot forget one of them: - what sits under a block cursor, which fell back to the terminal library's own default rather than this theme's background, so the character under the cursor could come out invisible; - what colour to write selected text in, chosen from how light the selection is, because a theme with a pale selection and one with a dark selection cannot both use the same text on top, and dragging over a line you then cannot read is not a selection; - a quieter selection for a pane you are not looking at, blended halfway back to the background so it holds its place without competing. Every shipped theme is now checked for selection contrast, so a new one cannot arrive with text you cannot read on it. **A hollow cursor in the pane you are not typing into.** With several panes open, two solid blocks both looked like the live one. ## 0.52.0 — 2026-08-28 16:37 PDT **The pane says why it looks the way it does.** A tmux window has one size, shared by everything attached to it, so whichever browser is in front sets it and the rest draw at their own. And a CLI that paints its own prompt box has its columns kept and its picture shrunk, because narrowing the grid is what stacks the box. Both are deliberate and both look like a fault from the outside: text smaller than you left it, or a band of dead space down one side. The status strip now says which is happening, with the real numbers, and stays quiet the rest of the time, which is almost always. Working out *which* took some care. Asking the terminal what it would fit does not detect it, because each browser fits its own box quite happily and has no idea the window underneath belongs to somebody else. Comparing against the size tmux reports does detect it, but that arrives on a three second cycle, so a browser that had just resized itself accused a window that was not there. It compares the poll's clock against its own last claim instead, which is exact, and which correctly tells whichever browser is not winning. **Every window in a session is held at its size, not just the one in front.** Windows are locked individually because setting the rule server-wide kills the tmux 3.4 server the next time a detached session is created, and `default-size` does not rescue it. Only the current window was being locked, so a window made afterwards inherited the loose rule and collapsed to the size of the next client to attach: 200x50 became 80x23 the moment a browser arrived. Invisible on the one-window sessions CLIque makes itself, and waiting on anything split inside tmux or adopted from another tool. ## 0.51.10 — 2026-08-28 16:11 PDT **The scrollbar no longer floats in the middle of the pane.** Opening the side panel next to a CLI that draws its own prompt box left the terminal's scrollbar stranded about four fifths of the way across, with terminal text on both sides of it. The terminal sizes its scrollbar from its own root element while the picture it draws follows the column count, so a pane that deliberately keeps a wide grid inside a narrower box left the two disagreeing by a couple of hundred pixels. The root is told the grid's real width now, and the two scale down together. **And the picture fits the room it actually has.** The fit was measured from the left of the pane rather than from where the terminal starts, which differ by the pane's padding, so the scaled picture came out a few pixels wider than its box every time. `tools/redraw_check.py` measures the gap between the scrollbar and the edge of the drawing: 220 pixels before this, none after. ## 0.51.9 — 2026-08-28 14:42 PDT **A second browser no longer leaves the first one drawn at half size.** A tmux window has one size, shared by everyone attached to it, so when another browser resizes it this pane's grid changes underneath. The box does not move, so the observer that normally triggers a re-layout never fired, and a boxed CLI's zoom is worked out from its column count: the scale stayed the one that suited a grid we no longer had. The pane ended up a half-width picture in a full-width box, with the terminal's scrollbar floating in the open space where the picture stopped. Measured at 47% of the pane's width before this, above 90% after. The grid changing is now a reason to lay the pane out again, and the size reported back to tmux is where the layout settled rather than the size that came in. `tools/redraw_check.py` opens a second, smaller browser on the same session and asserts both panes fill their width. ## 0.51.8 — 2026-08-28 14:34 PDT **The terminal can no longer paint over the side panel.** A boxed CLI keeps its own grid and is scaled to fit the pane, and that scale is worked out from a measured character cell. A fraction of a pixel of error there, multiplied by a couple of hundred columns, put the right edge of the terminal on top of the panel. Nothing in the pane legitimately draws outside its box, so the box clips now, and the check asks the question the overlap answered wrongly: just inside the panel's edge, is the panel what a click would land on. ## 0.51.7 — 2026-08-28 13:39 PDT **A resource the machine cannot report closes up instead of leaving a hole.** Swap and temperature were held invisible rather than removed, so a box with no sensor and no swap in use carried two blank columns in the bottom bar forever, and the readings either side of them sat oddly far apart. That was trading a permanent gap for a nudge that almost never happens. They are gone now when there is nothing to say, and drawn again the moment there is. ## 0.51.6 — 2026-08-28 13:32 PDT **A boxed CLI fills its pane when the side panel is open.** Claude, Grok and Gemini draw their own prompt box, and narrowing the grid under them stacks it, so CLIque shrinks the picture to clear the panel instead of taking columns away. Shrinking the picture shrinks every cell with it, which frees up rows the pane was never taking: the terminal sat in the top of its box covering about two thirds of the height, with a dead black band underneath it. It takes those rows now, and tells tmux about them. **And it gives the size back when the panel closes.** The zoom was decided on the pane's width and height together, so once the extra rows were taken the grid was taller than its box, which read as a fresh reason to keep zooming. The zoom is decided on width alone now, which is the only thing it was ever protecting. `tools/redraw_check.py` grew the case, against a boxed stand-in rather than a paid CLI: it measured 68% coverage before this and asserts better than 90% now, opening and closing. ## 0.51.5 — 2026-08-28 13:23 PDT **The pane settles on a frame instead of a stopwatch.** Opening the side panel, the sidebar or zen mode changes the terminal's width, and the refit that follows used to run twice: once on the next frame, which measured the box the pane had just left, and again 80ms later to correct it. The 80 was a guess. A resize observer hands out its callbacks after that first frame and before anything is painted, so waiting one more frame is the point at which everything measuring the pane has already run, and it is a fact about the browser rather than a number that happened to work. One toggle is now one refit, it lands sooner than the old one did, and toggling twice quickly collapses to a single refit instead of fitting against a box that is already gone. **Zen mode tells tmux the new size.** Folding away three bars is a bigger change to the pane than either of the other two, and it was the one that only ever refitted locally. **A pane that comes back the same size is repainted anyway.** tmux draws a client when something it tracks changes, so a layout change that left the grid the same size gave it nothing to redraw for, and the terminal kept the frame it already had until a keystroke provoked a new one. The browser now asks for that frame directly, over the socket it already has. It reaches one client, the one that asked, so nobody else's window moves. **`tools/redraw_check.py`.** Drives a real browser and asserts what the timeout was guessing at: one settle per toggle, the terminal's columns matching the tmux window's both ways round, and the repaint reaching this browser's view and no other. ## 0.51.4 — 2026-08-28 12:26 PDT **Search reaches past history, not just live sessions.** With the running-only filter on, a search already looked past it to find a stopped session. It stopped short of the past conversations in the sidebar: a folder whose only match was something you closed yesterday was dropped before its history was ever read, and Ungrouped was not drawn at all when nothing live was sitting in it. Both now count towards what a search can find. **The clear x in the search box works on the first try.** It was a 17 pixel glyph, and a click only counts when the press and the release land on the same thing, so a mouse that moved a little between the two did nothing. It is a 26 pixel target now and it fires on the press, which is also what makes it usable with a thumb. ## 0.51.3 — 2026-08-28 11:58 PDT **Notes no longer lose an edit when you switch session.** A note saves shortly after you stop typing, and switching tabs inside that window used to send the edit to the wrong session: the change was lost, and if the session you switched to had not been opened in that browser yet, its notes file was deleted outright. The save now carries the session it was made in. **Reminders no longer overwrite what you are typing.** The background loop that fires note reminders rewrote the whole file from a copy it had read seconds earlier, dropping anything typed in the meantime. Both writers now take the same lock and re-read before writing. **The temperature column hides where it said it would.** On a machine with no sensor the status bar kept showing an empty TEMP reading instead of stepping aside. It also falls back to the hwmon sensors now when the kernel's thermal zones report something implausible, rather than giving up on the first answer it finds. **Notes stop sticking on touch.** The panel and notes hover styles sit behind the same pointer check as the rest of the app, so tapping a note no longer leaves its action buttons lit on the row you last touched. They still appear on tap, through focus rather than hover. Also: the `reminder` webhook event is listed in `API.md` with the other four. ## 0.51.2 — 2026-08-28 11:20 PDT **A cleaner terminal redraw when the side panel opens.** Opening or closing the panel now settles the pane the same way toggling the sidebar does: it fits twice, once on the next frame and again once layout has settled, and pushes the new size to tmux, so a full-screen CLI redraws to the narrower width instead of spilling under the panel. **Search finds sessions even when the list is filtered to running ones.** Typing in the sidebar search now looks past the running-only filter, so you can find and open a stopped session without turning the filter off first. ## 0.51.1 — 2026-08-28 11:04 PDT **Uptime and temperature in the status bar.** The bottom bar now shows how long the box has been up, and its temperature when the machine has a sensor to read. Like swap, the temperature column hides itself where there is nothing honest to say, which is most VMs and containers. ## 0.51.0 — 2026-08-28 10:29 PDT **A docked side panel, and notes that are a real checklist.** The right edge now has an always-on icon rail that opens a panel to a feature for the session in front: Notes, Git, Session info, and Export. It is collapsed by default, and nothing in it runs while it is shut, so an idle panel costs the browser nothing. Drag its edge to resize, and Ctrl+J (or the rail) opens and closes it. Notes moved out of a text box and into a nested checklist. Each line has a checkbox, you can indent lines with Tab to make topics with sub-notes, push a line straight into the terminal to act on it, and set a reminder that reaches you through your webhook (the same one that pings you when a session is waiting) even when the panel is closed. Everything is per session and saved on the server. An older plain-text note is carried over the first time you open it. The Git pane shows the branch, what has changed, and a one-click checkpoint. The Session pane shows the directory, CLI, memory and uptime. The Export pane writes the scrollback to a file. Each one is scoped to the tab you are on. ## 0.50.97 — 2026-08-25 18:55 PDT **Zen mode.** "Zen mode" in the command palette folds away the sidebar, the tabs and the status bars, leaving just the terminal and the prompt for when you want to be heads-down in one session. A quiet button in the corner (or the palette again) brings everything back. The pane refits to the reclaimed space, so the CLI uses the full width while you are in it. ## 0.50.96 — 2026-08-25 18:48 PDT **Per-session notes.** Every session now has a scratchpad, on its menu (and in the command palette) as "Notes". Jot what a session is for, a to-do, or where you left off, and it persists across reloads and restarts. It is saved as a sidecar Markdown file under the panel's home, keyed by the session, so it never turns up as an untracked file in the project it is about. Emptying a note deletes it, and Esc or Cmd/Ctrl+Enter saves and closes. ## 0.50.95 — 2026-08-25 17:01 PDT **A soft interrupt for a running session.** "Interrupt (Ctrl-C)" is now on the session menu (long-press or right-click the row) and in the command palette. It sends Ctrl-C to the session in front, the gentle way to stop what an agent or a command is doing without killing the session or the tmux window behind it. The work stops and the session drops back to idle, ready for the next thing. ## 0.50.94 — 2026-08-25 16:56 PDT **Export a session's scrollback to a file.** The command palette has a new "Export scrollback to a file": it writes the whole tmux history for the session in front to a timestamped `.txt` under `.clique-exports/`, as a clean log with no colour codes, ready to keep, grep or share. It carries a short header (the session, its directory and the time) and reports how many lines it saved. A session that is not running has no pane to read, so it says there is nothing to export rather than writing an empty file. ## 0.50.93 — 2026-08-25 16:47 PDT **A status line for the session in front.** A thin strip under the tabs now shows, for whichever session you have open: the process it is running, its working directory, the git branch (with a count of changed files), how long it has been up, and how long it has been quiet. Every one of those facts already rode on the sidebar's poll; this lines them up for the one session you are actually looking at, so the state you care about is on screen without reading the row. It updates on the same three-second poll and hides itself when nothing is open. ## 0.50.92 — 2026-08-25 16:32 PDT **Checkpoint a repo before you let an agent loose.** A new **Checkpoint** action on any session in a git repo (long-press / right-click the row) saves the current HEAD, branch and the full uncommitted diff to a timestamped file under `.clique-checkpoints/`. Afterwards you can see exactly what the agent changed — or `git apply -R` the file to walk it back. It's a record, not a lock: it captures the state, it doesn't freeze the tree. The saved diff includes untracked files and is bounded the same way the review diff is, so one giant file can't turn a checkpoint into a download. ## 0.50.91 — 2026-08-25 16:25 PDT **A Nerd Font option for the terminal.** Many agent CLIs draw powerline arrows and little icons from the Unicode Private Use Area, and an ordinary monospace font leaves those as empty boxes. Settings → Terminal font now has a **Nerd Font** choice that uses whichever one you have installed — JetBrainsMono, FiraCode, Hack, Meslo and friends — and still falls back to a real monospace so the grid never goes proportional if you have none. The picker note says where to get one. (Shipping a font in the package is a heavier, later step; this makes the box-glyph problem fixable today for anyone who installs one.) ## 0.50.90 — 2026-08-25 16:02 PDT **The socket list for adopting a predecessor's sessions is configurable.** When CLIque takes over sessions left running by an older tool, it scanned a fixed set of tmux sockets. That set is now a default you can override with `CLIQUE_FOREIGN_SOCKETS` (a comma-separated list), for a box whose predecessor ran under a different name. The stale `muxpanel` entry — this tool's own name before the rename, with nothing left running on it — is dropped from the default. ## 0.50.89 — 2026-08-25 15:14 PDT **Drop a file onto the window.** Drag an image, a PDF, a log — anything named — anywhere onto CLIque and it lands in the open session's `.clique-drops` folder, with its path dropped into your prompt for the agent to open. It's the drag-and-drop sibling of pasting a screenshot, but for files with names, and it carries the same guard as everything else that touches disk here: the name is reduced to a plain basename (no wandering out of the directory), a credential name like `.env` or a key is refused, and a drop never overwrites — a name that collides gets a ` (1)`. Sidebar and tab drags are untouched; only a real file drag lights up the drop target. **Keep shared files from piling up.** Settings → Images now shows how much your dropped and pasted files are using, with a **Clear shared files** button, and an optional auto-delete of anything older than a number of days you pick. It's **off by default** — a shared file is your file — and it only ever touches the `.clique-drops` and `.claude-images` scratch folders, never your project. This is the answer to "won't drops fill the disk": they stay local so the CLI can actually read them, and this is the broom. ## 0.50.88 — 2026-08-24 13:15 PDT **Folders can wear an emoji.** A folder's menu now has "Set an emoji" — give a folder a 🚀 or a 🐛 in place of its colour dot, for faster recognition down the sidebar. Leave it blank to go back to the dot. ## 0.50.87 — 2026-08-24 12:37 PDT **Edit files in place.** Open a text file from a session — click a path, or the file sheet — and there's now an Edit button: change it and Save writes it back to disk (Ctrl/Cmd+S saves, Esc backs out). It's held to the same fence as the viewer: only files inside the session's own directory, never a credential like `.env` or a key, and only files that already exist — so an edit can't wander off or overwrite a secret, and it can't create files. A big file that was only shown in part can't be edited (you'd save back a fraction of it), and the write is atomic with the file's permissions preserved. ## 0.50.86 — 2026-08-24 12:19 PDT **Undo a stop.** Stopping a session now leaves a one-click "Undo" in the corner — start it again right where you were, instead of hunting for the greyed-out row. (Closing a tab already just detaches and keeps the session running; stopping is the explicit, confirmed one, and even that keeps the record and its draft.) ## 0.50.85 — 2026-08-24 12:14 PDT **Duplicate a session.** A session's menu (right-click, or long-press on a phone) now has "Duplicate — same directory, fresh CLI": a second, independent agent on the same work — same folder, same directory, same CLI, its own process. Handy for running two approaches side by side, or a shell alongside the agent working the same repo. It shares the source's directory as-is; making a fresh git worktree is still the separate choice in New Session. ## 0.50.84 — 2026-08-24 12:10 PDT **A clear × in the session search.** Type to filter the sidebar and a small × appears at the right of the box; click it to clear the search and see every session again. It stays hidden while the box is empty. ## 0.50.83 — 2026-08-24 11:58 PDT **Sessions name themselves from your first prompt.** Spin up a shell in a directory and it starts out called "tmp"; send your first real prompt and it renames itself to what you're actually doing — so a tray of "tmp" and "shell" becomes the work in front of you. It only ever fills in a name that was auto-generated: the moment you name a session yourself, this leaves it alone, and a one-word "y" won't trigger it. ## 0.50.82 — 2026-08-24 11:42 PDT **A confirm before a command that looks destructive.** Send or broadcast something matching a short list of catastrophic patterns — `rm -rf /`, `mkfs`, a force-push, `drop database` — and CLIque asks once before it goes, showing you the exact command. It's a guard against a fat-fingered slip, not a block: the pane still has a shell, and everyday `rm -rf ./build` is left alone on purpose, because a guard that cries wolf gets switched off. The pattern list, and the whole feature, are yours to edit or turn off in Settings → Notifications → Command safety. ## 0.50.81 — 2026-08-24 11:18 PDT **Lock a session read-only while you read it.** Reviewing a pane on a phone — scrolling its output, resting a thumb on the glass — could send a stray keystroke straight into the agent. A new lock (the lock button beside Run, or the session menu) holds back *all* input to that session: the prompt, Run and Shell, the on-screen keys, and live terminal typing. The tab shows a small lock, the terminal wears a "read-only" tag so a swallowed keystroke says why, and it is per-session and remembered across reloads — unlock when you want to type again. ## 0.50.80 — 2026-08-24 11:01 PDT **Each session gets its own hook token.** The token a state hook uses to report "waiting" / "error" used to be one shared secret sitting in every session's environment — so a token read out of one agent's pane could nudge *any* other session's status. Now every session is handed its own, bound to its id: it can report only its own state, is re-minted when the session resumes, and is revoked outright when the session is deleted. A token exfiltrated from one pane can no longer speak for another, and it dies with the session that held it. ## 0.50.79 — 2026-08-24 10:47 PDT **A file preview never hands back a secret.** The click-to-preview that reads a path an agent printed already stayed inside the session's directory; now the credential block stands on its own — enforced whether or not that directory fence is on — and it covers what actually sits on a dev box: `.env` and its variants, private keys (`.pem`/`.key`/`.p12`), token files like `.npmrc`, `.pypirc` and `.bw-session`, and whole credential folders (`.ssh`, `.aws`, …). Containment now resolves symlinks before deciding, so a link inside the directory can't point out of it to escape the fence. ## 0.50.78 — 2026-08-24 10:32 PDT **A stopped-on-error session now stands out.** A session waiting on you already turns its name bold; a session that stopped on an error wore only the small ring on its icon — so the one state you most want to catch was the quietest thing in the list. Now an errored session shows a bold name in the "something broke" colour, in both its tab and the sidebar, exactly as loud as one asking a question. ## 0.50.77 — 2026-08-23 16:13 PDT **Bring your own model keys.** A new **Models** tab in Settings lets you add any OpenAI-compatible provider (OpenRouter, Groq, Together, a local Ollama) or the Anthropic API, give it a model, and test the key on the spot. This is the foundation the coming assistant features (an intelligent inbox, a chat skin, a docked copilot) will run on — but the key handling is worth shipping on its own, because it's built to a real bar: - **Encrypted at rest.** Each key is encrypted with AES-256-GCM before it touches disk; the data key lives in a separate `0600` file, so a copy of `state.json` — from a backup or a sync — is ciphertext, not a key. - **Never leaves the box, never comes back.** The key is never returned to the browser (the panel only tells you whether one is *set*), never logged, and never rides the status poll. - **Can't be pointed anywhere dangerous.** Before a prompt and a key go out, the destination is checked: a cloud-metadata or internal-network address is refused, so a mistyped or hostile endpoint can't turn the panel into a proxy. Local models on loopback still work. The encryption comes from one optional extra — `pip install 'clique-panel[llm]'` — so the base install stays 100% dependency-free; without it, the panel refuses to store a key rather than write it in the clear. ## 0.50.76 — 2026-08-23 15:36 PDT **A heads-up when the box is getting full — never a blocker.** CLIque already watches CPU, memory, swap and load; now it weighs them against how many agent sessions are actually live and, when the box is genuinely stretched, drops a quiet, dismissible strip at the top: *"12 sessions, ~1.2 GB free — heavy for this box. Idle ones auto-reap in 6h."* It fires on any of the real signals — available RAM near the floor, swap climbing, load sustained above the cores, or the session count past a soft ceiling worked out from this box's RAM and the **measured** cost of the sessions it's actually running (not a fixed guess). The New-Session form shows that ceiling when you're near it ("this box comfortably runs ~8 agents"), and Start is never disabled — the guard warns and gets out of the way. It ties into the levers that already exist: idle sessions auto-reap, and Reclaim is offered right there when there's something leaked to reclaim. All of this is the read-only sampling the status bar already does, so it costs nothing and nothing leaves the box. ## 0.50.75 — 2026-08-23 14:11 PDT **A second window opens clean, and closing one collects its tabs.** Opening the panel in a second window on the same computer used to clone the first window's strip. Now, when another window is already open, a fresh one starts empty — a second screen is a fresh desk — and you move over just the sessions you want. Close a window and its open tabs are handed to the remaining (primary) window rather than dropped, on a short grace so a reload is never mistaken for a close. Each window still remembers its own strip across a reload, and only the primary window writes the shared workspace, so a clean window never wipes the seed a fresh single window restores from. ## 0.50.74 — 2026-08-23 13:59 PDT **Move a session to another browser window.** Open the panel in two windows — one per screen — and they find each other over a BroadcastChannel (no server, nothing over the wire). A "Move to window …" item on any session's menu (its gear or a right-click) hands the session to the other window, which flashes its edges so you can see where it landed; the source lets go of the tab. A small "⧉ N" chip in the header names which window you're looking at, and each window remembers its own strip of tabs across a reload. Same-browser for now — cross-*device* (phone and desktop) would need a server relay. ## 0.50.73 — 2026-08-23 09:28 PDT **Sharper "needs you" detection for every CLI.** The generic waiting patterns — the ones that surface a prompt for a CLI CLIque knows nothing about (Codex, Gemini, Cursor and the rest) — now match case-insensitively, so a default-capital `(Y/n)` or `[y/N]` is caught; recognise a selection menu drawn with pointers other than `❯`; and pick up an "(Use arrow keys)" menu hint. New tests lock in both the prompts that must fire and the finished-turn output that must stay silent, so the inbox does not cry wolf. ("Working" was already CLI-agnostic — it reads the activity clock and content, not any vendor.) ## 0.50.72 — 2026-08-23 09:12 PDT **A finished turn no longer reads as "needs an answer."** Claude Code's `Stop` hook fires at the end of *every* turn, and it was reporting `waiting` — the same signal a real question raises — so an autonomous session that had merely finished a turn showed up in the inbox as needing you. `Stop` now clears instead: a genuine wait still surfaces, from the idle Notification (~60s of no input) or a permission prompt, and a finished-but-unopened turn shows as "finished — not opened yet" rather than "needs an answer". ## 0.50.71 — 2026-08-23 08:56 PDT **Broadcast to a chosen set, not just everyone.** The broadcast sheet now lists the live sessions as a checklist grouped by folder — tick "All live sessions", a whole folder, or any handful, and the count and the Send button follow the selection. A new optional `ids` on `POST /api/broadcast` restricts the send to an explicit list of session ids; `folder` and all-sessions still work as before. ## 0.50.70 — 2026-08-23 08:44 PDT **A leaner sidebar header.** The top of the sidebar had grown to eight controls. New folder, adopt, broadcast and the board now sit behind a single "⋯" overflow menu, leaving the bell, the running-only filter, that menu, settings and the collapse chevron on the bar. Nothing is lost — the occasional actions are one tap deeper. ## 0.50.69 — 2026-08-23 08:31 PDT **Scope the read surface — the state-hook token can no longer read the panel.** The `attention`-scoped token handed to every session (so a state hook can report its own status) was accepted across the whole read surface: `GET /api/*` and the `/ws` attach checked only that *a* token was valid, never its scope. An agent that read its own `$CLIQUE_TOKEN` — including a prompt-injected one — could with it stream another session's live terminal, read every past prompt and transcript, and read arbitrary host files. Reads now require a `read` scope (a cookie counts); that token reaches only `POST …/attention`, the one thing it is for. Nothing legitimate read with it. Proven by `tools/scope_check.py`. Also hardened: file previews are now fenced to the session's working directory by default (credential files blocked; opt out with `CLIQUE_FENCE_READS=0`); `webhook_url` is withheld from API tokens, since its path can itself be a credential, while the cookie operator still sees it; `git worktree` and `tmux send-keys` gain `--` guards so a `-`-leading branch or key cannot inject a flag; the session cookie is `Secure` whenever the request arrived over HTTPS, even without `CLIQUE_TRUST_PROXY`; and a 500 no longer echoes internal paths. **Modal sheets render as modals again.** The board, broadcast, inbox and review-changes sheets shipped without their overlay rule and drew as unstyled full-width strips at the foot of the page — the board fell entirely below the fold, so its button looked dead. They now open centred over a backdrop like every other dialog, guarded by `tools/overlays_check.py`. ## 0.50.68 — 2026-08-22 21:54 PDT **Start a fleet in one click.** The new-session form takes a "How many" count now — up to 20 — and spins them all up at once, each with a numbered name. Turn on a worktree and each gets its own branch (`-1`, `-2`, …), so a fleet of agents can work the same repo without treading on each other's files. New endpoint `POST /api/sessions/spawn`, reusing the ordinary per-session create; one that fails to start is reported, not fatal to the rest. ## 0.50.67 — 2026-08-22 21:41 PDT **A board of the whole fleet.** A new grid button opens every session as a card in a column for what it is doing — Working, Needs you, Idle, Stopped — filled from the same authoritative status the sidebar ring reads, so a card moves the moment a session's state does, and the poll re-ranks it live while the board is open. Tap a card to jump to that session. The whole-fleet glance the one-per-line sidebar is not. ## 0.50.66 — 2026-08-22 21:33 PDT **See what a session has spent.** A "Usage" action on any session that keeps a transcript (Claude) reads its own log and totals the tokens — input, output, and the two cache figures — across every assistant message, shown as a monospace breakdown. Read on demand, never on the poll, via a new `GET /api/sessions//usage`. A session with nothing logged yet, or a CLI that keeps no usage, says so plainly. ## 0.50.65 — 2026-08-22 21:24 PDT **The inbox tells "wants approval" apart from "asked you something".** A Claude Code permission prompt and an open question both mean "needs you", but they want different answers — so the state hooks now report which (the Notification matcher: permission_prompt vs idle_prompt), carried through the attention endpoint as an optional `note`. A permission prompt in the inbox gets one-tap **Approve / Deny** (Enter accepts the highlighted default, Escape cancels); a question keeps the reply box. Everything else is unchanged. ## 0.50.64 — 2026-08-22 21:14 PDT **Broadcast: one message to every session at once.** The clearest form of "one cockpit driving many" — a megaphone in the header opens a composer, you type an instruction (or leave it empty to send a bare Enter, a "carry on" to all), pick all sessions or one folder, and it is typed into every live session in one go. It shows how many it will reach before you send. New endpoint `POST /api/broadcast` (write-scoped), reusing the existing per-session send path; dead sessions are skipped and one session failing does not sink the rest. ## 0.50.63 — 2026-08-22 20:33 PDT **Security hardening of the new state hooks and review diff, and bell polish.** An independent audit of 0.50.60–0.50.62 turned up one issue that mattered and several worth fixing: - **The state-hook token is now `attention`-scoped, not `write`.** It lives in every session's environment, and a `write` token there was a real hole — an agent, or a prompt-injected one, could read it and spawn a shell or drive another session. The new scope permits only the status nudge to the attention endpoint; verified that `/send` and `/api/sessions` refuse it. - **The review diff is bounded and complete.** Total size is capped so a giant file cannot freeze the page; new files inside a brand-new directory now show (via `git ls-files -z`, which also fixes spaced and unicode names); and a repo with no commit yet shows its first staged work. - **`hook.token` is written 0600 at creation**, not chmod-ed a beat later. - **"Review changes" shows for a dirty detached HEAD**, not only a named branch. The bell also gets a touch more space from the filter button and now rings for a second every ~16s while something is waiting — quiet under reduced-motion. ## 0.50.62 — 2026-08-22 20:13 PDT **Review what an agent changed, and comment straight back to it.** A session in a git repo gets a "Review changes" action that shows its uncommitted diff — tracked edits and the new files it wrote, added and removed lines coloured — and a comment box that sends what you type as the agent's next message. The review note and the follow-up are the same message: read the change, say what to fix, done. No new write-side endpoints; it is git's own diff and the send path that already existed. Also fixes a long-standing crash in the hover-to-peek handler — a null dereference when the mouse left empty sidebar space with nothing being peeked. ## 0.50.61 — 2026-08-22 19:57 PDT **A "needs you" inbox — see who is waiting, and answer from anywhere.** A bell in the header carries a count of the sessions waiting on you (a question, an error, or one that just finished), and the browser tab title carries it too — "(3) CLIque" — so a backgrounded tab says it at a glance. Open the bell for the list, most-urgent first, and answer right there without opening the pane: type a reply, or send it empty to accept the highlighted default (which is how a Claude Code permission prompt says yes). It is one sheet, touch-sized, so the same answer works from a phone. Built on the authoritative state from 0.50.60 and the send path that already existed — no new endpoints. ## 0.50.60 — 2026-08-22 19:42 PDT **Claude Code sessions report their own state now, instead of being guessed at.** CLIque could not always tell "waiting for you" from "running a slow test" — it read state from output activity and prompt patterns, and an agent redrawing a spinner kept the clock looking busy. A Claude Code session now launches with a `--settings` block whose hooks POST its real state to CLIque: Notification and Stop mean it yielded and is waiting on you; UserPromptSubmit means you answered and it is working again. That authoritative signal outranks the activity guess wherever the state is formed — the sidebar ring and the `/wait` endpoint are exact, not fuzzy. Nothing to install: the hooks ride in per session via `--settings`, so they touch neither your global Claude config nor your repo, and they no-op for any session CLIque did not launch. Other CLIs keep the pattern-and-activity fallback; `hooks = true` in clis.toml opts a CLI in. ## 0.50.59 — 2026-08-22 19:06 PDT **GPU rendering stays on by default, and now knows when to bow out.** WebGL (0.50.58) is a real, felt speed-up, so it stays the default — but a browser allows only ~16 live GPU contexts, and CLIque is built to hold many panes. Past that the browser drops the oldest pane's context; that pane, always a background one, now falls back to canvas on the spot, so nothing you are looking at ever breaks. If those losses keep happening — a weak GPU, a driver that resets, far more panes than the cap — CLIque offers, once, to turn the GPU off for steadier canvas drawing. A **per-device toggle** in Settings › Rendering does the same by hand (per device, because a desktop has a GPU an old phone may not, and the choice should not follow you across machines). The frame-batching from 0.50.58 — one terminal write per animation frame, not one per network packet — is unchanged. ## 0.50.58 — 2026-08-22 18:06 PDT **Smoother terminals: GPU rendering, and output batched to the frame.** Three changes to how panes draw, each felt most with many live sessions at once. - **A WebGL renderer, with a canvas-then-DOM fallback.** The cell grid now repaints on the client's GPU wherever the browser offers a WebGL2 context — far less CPU than the canvas renderer with a dozen panes open. A phone, a VM or a remote context that refuses or loses the GPU drops to canvas, and then to the built-in DOM renderer; WebGL is used where it works and never required. - **One write per frame, not one per network packet.** A flood of output — a `yes` loop, a giant diff, a screen cleared and redrawn — used to mean hundreds of parser-and-render passes a second. The bytes are now coalesced and handed to the terminal once per animation frame: same output, a fraction of the work, and the scroll jank goes with it. - **Off-screen transcript turns are skipped.** A long transcript only lays out and paints the turns in view (`content-visibility`), so opening a big one is quick no matter its length. All safe because closing a tab now disposes the renderer before the terminal (0.50.57) — the same fix that made it possible to turn WebGL back on. ## 0.50.57 — 2026-08-22 16:59 PDT **Closing a tab now really closes it, and Kill really kills.** The renderer addon threw while a terminal was being disposed -- it tried to restore the DOM renderer against an already-torn-down linkifier -- and that exception aborted closeTab half-way: the tab stayed on screen, and worse, the kill request that runs right after it never fired, so the session lived on. closeTab now disposes the renderer while the terminal is still whole, then tears the terminal down under guards, so nothing a renderer does can stop a tab from closing. Kill from the tab gear and Kill from the sidebar right-click both go through this, and both are now reliable. ## 0.50.56 — 2026-08-22 16:30 PDT **Kill session now actually kills it, every time.** Stopping a session used to report success unconditionally, even when a tmux hiccup or an adopted session on a foreign prefix meant the process never died -- so the tab closed and a live session was left behind. It now kills, verifies, force-kills anything that survived, and reports the real outcome; the browser surfaces a retry if it ever truly will not stop. Both the tab's gear menu and the sidebar right-click use this path, so "Kill session" is now reliable from either one. ## 0.50.55 — 2026-08-22 16:03 PDT **The leaked-sessions warning is now hard to miss.** It was a correct but quiet line at the foot of the sidebar, easy to scroll past. Now it is a warn-tinted bar with a warning mark, a bold count, and a filled Reclaim button, pinned where it always was but loud enough to notice. Nothing changed about when it appears: only while there is leaked memory to reclaim, and gone the moment you reclaim it. ## 0.50.54 — 2026-08-22 11:06 PDT **Google Antigravity is a first-class CLI now.** Its `agy` command is recognised, selectable in the New Session dialog, and carries its own tinted mark — a session started as Antigravity is no longer an unlabelled pane. It draws its own screen, so the alternate-screen scroll fix applies to it as well. Purely config plus an icon, the way adding a CLI is meant to be. ## 0.50.53 — 2026-08-22 10:58 PDT **Scrolling works in every CLI now, including Claude and Grok.** A full-screen app on the alternate screen keeps no terminal scrollback — it owns its own view — so the wheel was being swallowed into a buffer tmux only ever redraws in place, and scrolling did nothing. The panel now knows, per session, whether the pane is on the alternate screen (`alt`), and forwards the wheel to the app as mouse-wheel events there: it scrolls its own conversation and the redraw streams back. A shell or any normal-screen output still scrolls the pane's own 20k-line scrollback exactly as before. ## 0.50.52 — 2026-08-22 10:41 PDT **Drive CLIque from an agent.** Every session now reports a one-word `state` — `working`, `waiting`, `error`, `idle`, or `stopped` — and a new `GET /api/sessions//wait?for=idle,waiting` blocks until it reaches one of those or times out, so an agent can start work and wait for it to finish or come back asking instead of polling the whole panel. With that plus the worktree option, an agent can run one task across many repos in parallel and collect the results. The workflow — mint a token, start sessions, send, wait, read — is written up as a skill in `skills/drive-clique/SKILL.md`. ## 0.50.51 — 2026-08-22 10:32 PDT **Run an agent in its own git worktree.** Starting a session in a repo now offers "Run in a new git worktree": CLIque makes an isolated checkout on a fresh branch and runs the agent there, so several agents can work the same repo at once without touching each other's files — no more two sessions clobbering one working tree. Delete the session and the worktree goes with it, but only when it is clean; a worktree with uncommitted work is left alone so nothing is lost. Scriptable too — `POST /api/sessions` takes `worktree` and `branch`. ## 0.50.50 — 2026-08-22 09:06 PDT **Filter the sidebar to just what is running.** A funnel button at the top of the sidebar hides every stopped session — and any folder left empty by that — so a list of two dozen collapses to the handful you are actually working in. It lights up while it is on, and the choice is remembered on this device (like the sidebar's width and whether it is shown), so it survives a reload without following you to your phone. ## 0.50.49 — 2026-08-21 20:58 PDT **Read back a conversation that scrolled away.** Claude draws over the terminal's alternate screen, so once its output scrolls off it is gone from the pane — but every turn is on disk. A new "View conversation" on a Claude session's menu opens its transcript in the file sheet: user prompts and the assistant's prose, oldest first, the thinking and tool calls left out. Read from a bounded tail of the transcript, never the whole file, and keyed off the session id Claude was launched with, so it is always the right conversation. Offered only where there is a full transcript to show. ## 0.50.48 — 2026-08-21 20:46 PDT **Leaked sessions are visible and reclaimable.** A tmux session left running with no record behind it — the record removed without stopping its process — used to hold its memory invisibly, off the list and past any control. The sidebar now shows a quiet line when any exist: how many, how much memory, and a Reclaim button that stops them. Detection skips anything younger than the grace window, so a session mid-creation is never mistaken for a leak, and a mux that belongs to a real record is never killed. Completes the memory trio with per-tab RSS and idle reaping. ## 0.50.47 — 2026-08-21 20:36 PDT **A directory can no longer be mistaken for the state file.** `Store` now refuses a path that is a directory instead of quietly renaming it: the atomic save writes `state.json.bak` beside `state.json`, and pointed at `$CLIQUE_HOME` rather than `$CLIQUE_HOME/state.json` that rename moved the whole home aside. A caller mistake now raises on the spot rather than displacing a workspace. ## 0.50.46 — 2026-08-21 20:28 PDT **Idle sessions reap themselves and resume on click.** A tab left untouched for six hours has its process stopped — freeing its memory, ~700 MB for an idle Claude — and its tab greys out. Click it and the session comes back exactly where it was: the state was on disk the whole time, so nothing is lost. Ten open tabs now cost what two do. Only a session that can actually be resumed, that no browser is attached to, and that is not mid-task is ever touched; a shell, which has nothing to resume, is left alone. The window is the `reap_idle_hours` setting — set it to `0` to switch reaping off. ## 0.50.45 — 2026-08-21 20:11 PDT **Every session shows its memory in the sidebar.** A small dim figure on each row — the resident memory of the CLI and everything it spawned, from one cached /proc walk for the whole list. It turns "which tab is eating the box" from a guess into a number: a stack of idle Claude tabs at ~700 MB each is suddenly visible. Groundwork for reaping the idle ones. ## 0.50.44 — 2026-08-21 19:52 PDT **Host header with a `userinfo@` part is refused.** `host_allowed` took the text after an `@`, so `Host: evil.com@127.0.0.1` read as the safe `127.0.0.1`. Browsers never send that, so any `@` in a Host is now a rejection. Closes the last of the three-model security review; what remains is defense-in-depth for exposed self-hosting (a WS-aware slow-header timeout), not a reachable bug. ## 0.50.43 — 2026-08-21 19:50 PDT **An on-screen key row on the phone.** A soft keyboard cannot send Esc, Tab, Ctrl+C or the arrow keys a TUI lives on — so a row of them now sits above the input on a narrow screen, tapping each straight into the pane in front. It shows only with a session up, only on a touch layout, and scrolls if it runs out of room. That was the last thing between the phone build and actually driving an agent from it. ## 0.50.42 — 2026-08-21 19:43 PDT **Two more from the sweep.** Static files are served with a real containment check instead of a string prefix, so a sibling directory named `web-anything` cannot slip through. And webhook deliveries run on a small fixed worker pool behind a bounded queue rather than a thread per call — so a caller hammering the test endpoint cannot pile up daemon threads; a full queue drops the delivery, which the notification model already allows. ## 0.50.41 — 2026-08-21 19:41 PDT **`X-Forwarded-*` is believed only behind a trusted proxy now.** Host and scheme were taken from `X-Forwarded-Host`/`X-Forwarded-Proto` no matter who sent them, so any client could set `X-Forwarded-Host` and walk past the DNS-rebinding gate that runs on `Host`. They are honored only when `CLIQUE_TRUST_PROXY=1` declares a proxy sets them; otherwise `Host` is the truth. This deployment runs behind tailscale, so the flag is set here. ## 0.50.40 — 2026-08-21 19:37 PDT **Killing a session from the tab closes it cleanly, right away.** Kill closed the tab silently and then waited on the request before repainting — so the dead tab and a blank pane hung on screen until the kill came back, and whether a poll fired in between is what made it feel hit-or-miss. It now closes the tab, selects the next one, and repaints immediately, then kills in the background. Same for removing a stopped session. ## 0.50.39 — 2026-08-21 19:06 PDT **The same-origin check compares the port too, and understands IPv6.** It split the host on ":" and dropped the port, so a page on another port of the same host — `http://127.0.0.1:9999` against the panel on `:3200` — counted as the panel's own origin and could drive it (the CSWSH the check exists to stop), and IPv6 literals were mangled to `[`. It now parses the origin and compares host and port, leaving scheme out so a tunnelled panel is never locked out of itself. A `CLIQUE_ALLOWED_HOSTS` name is still trusted as an origin, as before. ## 0.50.38 — 2026-08-21 18:54 PDT **A fenced read mode for public deployments.** Set `CLIQUE_FENCE_READS=1` and the file glance can no longer read outside a session's working directory, and refuses obvious credential files (`id_rsa`, `.env`, `.aws/`, and the like) even inside it. Default deployments are unchanged — the trusted-local model still lets an authenticated user read any path, because they already have a shell as this user. This is a self-hosting gate: the flag is what makes read tokens safe to hand out on an exposed instance. ## 0.50.37 — 2026-08-21 18:42 PDT **Revoking an API token now sticks.** `last_used` was written back to disk on the request path, so a `token revoke` from the CLI could be undone the instant the running server persisted its stale in-memory list over it. The timestamp is now kept in memory and flushed on shutdown only; the file is written on mint, revoke, and exit — the three moments that change which tokens exist. A revoke is durable. ## 0.50.36 — 2026-08-21 18:37 PDT **Security pass, part one.** Six fixes from the three-model review: `PATCH /api/settings` no longer returns the webhook secret in its response; the file glance reads only the 256 KB it shows instead of loading the whole file into memory; a negative `Content-Length` can no longer hang a request thread on a read-to-EOF; drafts and names are capped so they cannot bloat the state poll; the terminal filter bounds an unterminated control sequence instead of buffering it without limit; and the CSP allows WebSockets only to the panel's own origin. None of these were reachable in the single-user loopback model without a credential — all of them matter the moment CLIque is self-hosted. ## 0.50.35 — 2026-08-21 17:57 PDT **Closing and reopening the sidebar no longer leaves the pane the wrong size.** The toggle refit the terminal at the instant of the change — before the layout had settled — and never told tmux the new width, so a boxed CLI came back scaled against the old size, with a stray scrollbar and dead space beside it. It now refits on the next frame and again once settled, and pushes the new width through to tmux. ## 0.50.34 — 2026-08-21 17:47 PDT **The wheel scrolls the pane's history again, even when the CLI grabs the mouse.** Claude, Grok and anything else that turns on mouse tracking was being handed every wheel tick by the terminal, so scrolling up did nothing and output that went off the top was unreachable — on a panel built for watching output, the one thing you needed. The wheel now scrolls the pane's own 20,000 lines of scrollback instead, and scrolling up pauses following so the view holds. No Shift required; non-mouse-mode CLIs are unchanged. ## 0.50.33 — 2026-08-21 17:38 PDT **First pass at a phone layout.** On a narrow screen the sidebar is now an overlay drawer over a full-width pane, not a column fighting the terminal for room: the drag-resizer steps aside, a tap on the rail slides the drawer in over a dimmed pane, and opening a session slides it back out. The rest of the phone work — an on-screen key row, the input bar's spacing — is still to come; this is the layout it hangs on. ## 0.50.32 — 2026-08-21 17:17 PDT **Pin a session and it floats to the top of its group.** Right-click or long-press a session and "Pin to top" keeps it above the newer ones that would otherwise push it down — the handful you keep coming back to, marked with a star and held above recency, in the sidebar and inside its folder. Unpin drops it back into the flow. ## 0.50.31 — 2026-08-21 16:49 PDT **Search everything you have typed, and send it again.** The command palette has a prompt mode — the "Reuse a past prompt" command, or a `"` to open straight into it — that fuzzy-searches every prompt you have sent, per project and across all of them, newest first. Click one and it drops into the box to edit or send; a terminal-mode CLI gets it in its own input, no newline. Nothing is logged twice: the prompts come from the CLIs' own history — a prompt-log read whole, a transcript from a bounded tail, so a thirty-megabyte one is never walked. ## 0.50.30 — 2026-08-21 16:34 PDT **Changing the terminal font now resizes the CLI to match.** The bottom-right font stepper (and the settings slider) resized the grid drawn in the pane but never told tmux, so the CLI kept wrapping at the old width — dead space, and a pane that looked a different size from one tab to the next. The new size is pushed to tmux on the change now, the same as a window resize already was. ## 0.50.29 — 2026-08-21 15:08 PDT **Copy the last lines without dragging across them.** Selection and the visible screen already copied; the palette now carries "Copy the last 50 lines" — the recent output, scrollback and all, ending where the pane last wrote, wherever the view is parked. It counts lines rather than guessing where a reply began: CLIque does not read what the CLI said, so a fixed window is the honest unit. ## 0.50.28 — 2026-08-21 15:05 PDT **Switching to a tab lands you in the prompt box; Escape hands the pane back.** A switch used to drop the cursor into the terminal, so typing a prompt meant clicking the box first. Now the box takes focus on a switch, ready for the next prompt — and Escape from the box moves focus to the pane, to scroll it or type into the CLI's own input. Terminal-mode CLIs, which own their input, keep the pane focused as before, and a phone is left alone so the on-screen keyboard does not spring up on every tab. ## 0.50.27 — 2026-08-21 14:56 PDT **Hover a row or tab and the tooltip shows the last few lines.** A status ring tells you a session changed; it does not tell you what it said. Rest on the row — in the sidebar or on the tab bar — and the native tooltip now carries the last few content lines of the pane, captured only when you stop there, so a quiet sidebar still costs nothing. No popup: the browser draws the tooltip, which is why it cannot cover a menu or eat a click. Touch, which has no hover, keeps its long-press menu. ## 0.50.26 — 2026-08-21 14:34 PDT **Stopping a busy session now says what you would interrupt.** Kill asked a flat "Stop this?" whether the session was idle or an agent was mid-task with an unsent prompt still in the box — the same reflex click either way. It now names what is live: *"X is still working and has an unsent draft — stop it anyway?"* Nothing is lost regardless — the session stays in its folder, draft and all, and starts again — but the question is no longer one you answer without looking. ## 0.50.25 — 2026-08-21 09:39 PDT **A login link that wraps is still one link.** Codex (and anything else) prints a URL that used to split across lines, so a click only got the first half. The pane now joins those lines. If the login tries to come back to localhost on this box while you are on another machine, clicking it backs out of that flow — pick **Sign in with Device Code**, which is the one that actually works from here. ## 0.50.24 — 2026-08-21 09:30 PDT **What's new sits on the bottom bar.** After an upgrade, a mark appears next to the stats. Click it and you are in Settings → Changelog. The sheet itself holds the last five releases; the rest is a link to the file on GitHub. Opening the notes clears the mark. ## 0.50.23 — 2026-08-20 21:08 PDT **Restarting the panel now updates tmux's own settings.** tmux is supposed to outlive the panel, so a new history length used to stay stuck at whatever it was when tmux first started. Existing windows also get their size lock put back, so an old session cannot start autosizing again. ## 0.50.22 — 2026-08-20 21:05 PDT **Shrinking the window no longer stacks Gemini's box on itself.** Claude, Grok and Gemini were being told the pane got narrower, so they wrapped their old chrome into extra copies. The pane now zooms to fit instead — same conversation, whole thing still on screen. A phone still resizes for real, because zooming that far would make the type too small. **Switching tabs no longer blanks the pane.** Hidden terminals stay painted, just not in front, so Chrome cannot throw the picture away. **Keys typed while it says Reconnecting… land when it is back**, instead of vanishing. ## 0.50.21 — 2026-08-20 19:00 PDT **Copy from a Claude, Grok, or Gemini pane works like selecting on a page.** Those CLIs were telling the browser “the mouse is mine,” so a drag never became a selection. The browser no longer hears that. A click still goes to the CLI. After you copy, the highlight clears so the next Ctrl+C is interrupt. Ctrl+Shift+C copies whatever is on the screen if nothing is selected. The command palette can do that too. ## 0.50.20 — 2026-08-20 18:35 PDT **Drag across the pane to copy.** A CLI that is listening for clicks used to swallow the drag, so there was nothing to copy — and on a Mac there was no modifier that would get it back. A drag is a selection now, and letting go puts it on the clipboard. A click still goes to the CLI. Ctrl+C and the Copy button still work. ## 0.50.19 — 2026-08-20 18:24 PDT **Opening a smoke test can no longer take over the window you are in.** The checks used to talk to this panel and the same tmux server, so a `/tmp` shell could land on the tab you were looking at. They now run on their own panel, their own state, and their own tmux socket, and they cannot see this one. ## 0.50.18 — 2026-08-20 18:13 PDT **Its own window, no browser around it.** Install as an app — from Settings → About, the command palette, or the browser’s own Install. Full screen is the button next to the shortcuts, or Ctrl/Shift+F. Not Electron: the panel is still 24 MB, and a restart still does not kill your sessions. ## 0.50.17 — 2026-08-20 18:07 PDT **Select text in the pane and Ctrl/Cmd+C copies it.** Every CLI, every tab. With nothing selected, Ctrl+C still interrupts. A Copy button appears while something is selected, so a phone can do it too. Right-click on a selection copies. A pane redraw no longer throws the selection away the moment you go to copy it. ## 0.50.16 — 2026-08-20 18:01 PDT **The pane you are looking at stays the pane you are looking at.** A tab warming in the background used to refit this one and leave it blank — Chrome had thrown the hidden canvas away, and a same-size fit did nothing. Coming back, or switching tabs, now redraws it. A full refresh should no longer be the fix. ## 0.50.15 — 2026-08-20 17:52 PDT **The pane keeps the size of the window you are looking at.** tmux no longer resizes itself when a background tab reconnects, a second window opens, or the panel restarts while you are elsewhere. Coming back fills it. A collapsed measure cannot shrink it to a screen of dots. ## 0.50.14 — 2026-08-20 17:21 PDT **Each row names the git branch it is on.** And how many files have changed, when that is not zero. The folder tree was already many repos and said so nowhere. A directory that is not a repo looks the way it always did. ## 0.50.13 — 2026-08-20 17:01 PDT **Coming back fills the pane again.** A restart — or a tab reconnecting in the background — could leave tmux smaller than this window. Coming back kept the dots, because the panel thought the sizes already matched. It now takes this window’s size as soon as you look, and a background reconnect no longer shrinks the one you had. ## 0.50.12 — 2026-08-20 16:42 PDT **A path in the pane is a click.** Same as a URL: click it to look, copy it, or drop it into the prompt. Read-only — this is not an editor. Ctrl/Cmd+click skips the look and drops the path where you are typing. ## 0.50.11 — 2026-08-20 16:26 PDT **The tabs you left are the tabs you get.** Opening the panel at home used to save an empty strip over the one from work — the first load had not finished reading yet, and a closing window often killed the save. It now waits until the workspace is actually back, and a closing window uses a request that survives the tab dying. ## 0.50.10 — 2026-08-20 16:13 PDT **Folders stay folders when you open the panel on another computer.** A session you had filed was being pulled up into Running whenever it had no tab open, so a fresh browser looked like nothing was organised. The filing was saved. The tree was hiding it. Running is only the unfiled inbox now. ## 0.50.9 — 2026-08-20 14:52 PDT **Killing a session no longer deletes it from the folder.** It stops the CLI and leaves the row. Click it to start again — Claude and anything else launched with our session id come back with their own conversation. A shell starts again in the same place. Remove it from the sidebar is a separate choice, on a session that is already stopped. ## 0.50.8 — 2026-08-20 14:47 PDT **Pick a terminal font, and change its size from the bottom-right.** Five monospace stacks that exist on Windows, Mac and Linux — a missing font falls back instead of going proportional. `+` and `−` next to the shortcuts change the size live. Both live on the server, so a reload and another device keep them. ## 0.50.7 — 2026-08-20 14:24 PDT **The spinner only turns while something is actually working.** A CLI sitting on a question used to keep spinning, because the prompt still blinked and that looked like output. That is not work — work is paused, waiting on you. Three marks, three facts: - **spinning** — it is working - **slow pulse** — it finished, and you have not opened that tab - **two knocks** — it is asking a question (y/n, Do you want, a numbered choice). The same marks work for every CLI, not just Claude and Grok. ## 0.50.6 — 2026-08-20 14:17 PDT **Coming back to the tab grows the pane again.** Leaving CLIque in a background browser tab let something else shrink the window. Coming back kept the dots — the page was not counted as focused yet, so it stayed quiet. It now takes the size that fits this window as soon as you look at it. ## 0.50.5 — 2026-08-20 14:13 PDT **Clicking a tab no longer flashes someone else's pane.** A tab that had not warmed yet kept showing the one you were on until its terminal caught up — and if you clicked away in that wait, the late one stole the screen back. The tab you click is the one you see, immediately. ## 0.50.4 — 2026-08-20 14:07 PDT **A faint CLIque mark sits at the bottom of the left panel.** Large, quiet, just branding. It does not take clicks, and the list still scrolls over it. ## 0.50.3 — 2026-08-20 13:44 PDT **Change color on a folder actually stays open.** Clicking it rebuilt the menu into the swatch grid, and the same click was then treated as "outside the menu" and closed it. The picker stays up until you pick a color. It says Color, and there are twenty-four to pick from. ## 0.50.2 — 2026-08-20 13:37 PDT **Grok's prompt sits in the window again.** Making the largest view always win left the pane bigger than this window, so a full-screen CLI drew its input off the bottom. This window now takes the size that fits *it*, but only while you are looking at it — an unfocused one stays quiet, which is what stopped the dotted fight from coming back. ## 0.50.1 — 2026-08-20 13:33 PDT **The dotted pane stays gone.** Those dots are tmux filling space a smaller window stole — a second CLIque tab, or the phone. Each side kept asserting its own size, so a fix that grew the pane back lost the next time the smaller one spoke. The largest view now wins. A smaller one cannot punch a hole in this one. ## 0.50.0 — 2026-08-20 13:29 PDT **The sidebar rearranges like the tab strip.** You could already drop a session onto a folder to file it. Folders themselves sat still, and sessions inside one could not be put in an order. Drag a folder or a session and a line marks where it will land — same gesture as the tabs. Dropping a session onto another in a different folder files it there too. ## 0.49.2 — 2026-08-20 13:23 PDT **The outage banner follows the tabs you have open.** It already only spoke up when a provider was actually down, and never for a CLI with no feed. It was still asking about every session in the sidebar, so a Claude session you closed last week could put Claude's outage on the bar. Now it is the open tabs: close Grok, Grok is not asked about. Grok and Gemini still have no banner. They do not publish the same status feed Claude, Codex, Copilot and Cursor do, and inventing a scraper for them would be a number that goes wrong silently. ## 0.49.1 — 2026-08-20 12:26 PDT **The view count says what it is.** The number next to the green dot is live views on the box, not open tabs — so 11 with 6 tabs means a second window or a phone is still connected. Hover spells that out in the count you actually have. ## 0.49.0 — 2026-08-20 12:14 PDT **Switching tabs is a show, not a hook-up.** After a reload the other tabs used to sit as names until you clicked one, then wait a second while a terminal, a socket and a tmux viewer spun up. They now warm in the background once the one in front is up, so the first click already has the pane. Hidden tabs connect at the window's current size and do not resize the one you are looking at. If it feels worse than the wait, it comes out. ## 0.48.0 — 2026-08-20 11:26 PDT **The sidebar no longer jumps every three seconds.** The list was being redrawn on every poll, so a long sidebar snapped back to the top and a folder you had just opened slammed shut under your finger. It now only rebuilds when something actually changed — a ring, a name, a new session — and it keeps its place in the list. **A reload no longer attaches every tab.** Opening the panel used to spin up a live view for each saved tab, which is why a second window fought the first for the pane size. The strip still shows them all; only the one you are looking at is attached. Clicking a background tab, or Alt+1–9, is what opens it. **On a phone, the controls are actually there.** Close and settings on a tab, and the pencil on a folder, used to appear only on hover — which a finger does not have. They stay visible. A long press on a folder or a past conversation opens the same menu a right-click does. **And the follow button keeps its icon.** Pausing used to replace the arrow with a text glyph that wiped the drawing out. It now swaps to a pause mark in the same set. **Light theme text that was too faint is readable.** The muted colour failed the contrast bar on the light grey panel; it is darker now. Settings, the new-session dialog, menus and toasts ease in. The command palette does not — that one is opened all day from the keyboard. ## 0.47.0 — 2026-08-20 09:07 PDT **Tabs that do not fit stay reachable.** A strip of a dozen sessions ran off the right edge, behind a scrollbar that was itself hidden, so a session that was waiting or finished was simply gone. Names shrink first. What still will not fit lands in a **N more** control on the strip — always on screen, wearing the same working / waiting / error ring the tabs themselves wear — and a click lists every hidden tab, the ones that need you first. **And the one you are in is obvious.** It was a two-pixel tint on top of a chip that looked like every other chip. It now sits as the top of the pane — same fill, a thicker bar in the CLI's colour, the name in weight — and a session flashing for attention no longer paints itself like the current tab. **The ring spins only while a session is working.** When it finishes and needs you, the arc stops and the ring pulses — same motion on the sidebar as on the tab, which the scrolling list had been clipping. Idle still draws nothing. **The stats no longer shove the tabs.** Readings are fixed-width tabular numbers, so a decimal appearing or swap dropping to zero does not walk the strip sideways. They live in a status bar at the bottom now, with the keyboard shortcuts on the right — the top strip is for sessions. ## 0.46.0 — 2026-08-19 16:54 PDT **Ctrl+V pastes.** It did nothing at all before — xterm treats Ctrl+V as a control character, which means calling `preventDefault`, which stops the browser from ever firing a paste event. So the keystroke was neither delivered to the CLI in a useful form nor pasted. Ctrl+Shift+V worked, and nobody presses Ctrl+Shift+V in a browser without being told to. The cost is readline's quoted-insert, which is a fair trade for the commonest action there is. **Ctrl+C copies when text is selected.** With nothing selected it is still SIGINT — a CLI you cannot stop is worse than one you cannot copy from — but having deliberately selected a line, "interrupt" is not what anyone meant. The toast says what was taken: "Copied 3 lines", so a copy that grabbed the wrong thing says so rather than being discovered later. **Shift+Enter is a newline, not a submit.** A terminal sends carriage return for both, so a CLI cannot tell them apart and every Shift+Enter submitted the prompt — which is why writing more than one line meant giving up. It now sends the CSI-u encoding of "Enter with Shift held", which is what the editors people compare this to send. Verified rather than assumed: typed into a live Claude Code prompt, `ESC CR` did nothing useful and `CSI 13;2u` moved the cursor to a second line. **And the panel's own box grows further** — it stopped at seven lines, so composing a paragraph meant scrolling inside the box you were writing in. It now grows to two fifths of the window, which is a different number of lines on a laptop and on a phone, as it should be. **A tab says what its marks mean.** A tab can carry a status ring, an attention glow and an unread dot, and the tooltip said only the working directory — so the honest reaction to any of them was "what is that". Hovering now spells it out: *stopped on an error*, *waiting for you*, *new output since you last looked*. ## 0.45.0 — 2026-08-19 16:27 PDT **A pane squashed by another client takes its size back.** A tmux window has exactly one size and every client attached shares it, so a second browser — or a phone picking the session up — resizes the first one's pane out from under it. The result is not subtle: tmux fills the columns the window no longer has with dots, and the terminal reads as broken. Nothing noticed, because a client only ever spoke up when *its own* terminal changed size, and being resized by somebody else is precisely the case that produces no local change. The poll now carries the window's real size, and a browser that finds it differs from what it is drawing says so — so the dot-fill clears within one poll instead of persisting until something happens to jog it. Only the tab in front, and only on a real difference: two browsers both re-asserting on a timer would fight rather than settle. **And the tool that found this was causing it.** `visual_check.py` opened whatever was in the sidebar, which meant a second browser attaching to sessions someone was working in — doing exactly the thing described above to a live pane. It makes its own throwaway session now and deletes it afterwards. ## 0.44.0 — 2026-08-19 16:10 PDT **A session that wants you says what for, in the row.** The ring told you a session was blocked and you still had to open the tab to find out what on. The row now shows the last line it actually printed, in place of the working directory — which is the least urgent thing on screen at the moment something is asking you a question. The directory is still there on hover. This **replaces the hover preview**, and 126 lines of machinery go with it. A popup has to be summoned, positioned and layered above everything else, and each of those is a way to be wrong: the layering one broke right-click on every sidebar row for a while. A line that is simply there when it matters has none of those problems. `GET /api/sessions//peek` stays for scripts. The line skips a prompt sitting under it — the bottom of a pane is usually a cursor waiting, and quoting that back says nothing; the question is the line above. Frame is dropped the same way it already was: box rules, separators and bare prompt marks are not something a session said. ## 0.43.0 — 2026-08-19 16:03 PDT **The tinted greys keep their contrast.** 0.42.0 made a theme able to own the 256-colour greyscale ramp, and did it by walking from the theme's background to its foreground — which on a monochrome theme is a walk into a saturated colour. Shades an application meant as *subtle* came out as a wash, and the text on top of them had to compete with it. An application reaching for colour 233 has chosen how far from the background it wants to be. That choice is not ours to move; only the neutrality is. Each step now keeps exactly the lightness xterm would have given it and takes the theme's hue at a low saturation, so `#121212` becomes `#0f1510` rather than something darker and greener. Every contrast relationship survives, which is the difference between a themed terminal and a coloured one. The suite asserts the lightness across all 24 steps, that the ramp still climbs, and that the 6×6×6 cube is left exactly as xterm defines it — an application asking for colour 82 wants *that* green. How far a theme goes is a number on the theme now, not a constant in the code. ## 0.42.0 — 2026-08-19 15:59 PDT **Find a directory you have never opened here.** The picker knew everywhere you had already worked, which is the right first answer and no answer at all for a project you have not started a session in yet — exactly when you are least likely to remember where it lives. Start typing a path and the suggestions now come from the disk, the way a shell completes: a trailing slash lists what is inside, anything else matches the last segment against its siblings. The remembered directories stay in the list alongside them. **Grok's past conversations exist now.** Grok keeps one append-only log per project rather than a transcript per conversation — the directory name is the working directory, percent-encoded, and each line carries the session id, the prompt and when it was sent. CLIque only understood Claude's layout, so every Grok conversation was invisible and none of them could be resumed. Six were found on the machine this was written on, going back weeks. **Grok says when it is waiting for you.** It had no attention patterns at all, so a Grok session sitting on a confirmation looked exactly like one that had finished. The patterns are taken from what a real Grok CLI put on screen, not from documentation. **A theme can own the 256-colour greyscale ramp.** Themes define the sixteen ANSI colours; indices 16–255 are the standard xterm cube and nothing here touched them. Grok paints its background with colour 233, which is neutral `#121212` on every theme ever written — so on Trinity, a deliberately monochrome green theme, its pane looked untouched by a theme that had in fact been applied. Trinity now tints the greyscale ramp between its own background and foreground, and 233 renders `#051909` instead. Opt-in per theme: an application choosing colour 82 wants *that* green, but 232–255 means "a shade near the background", which is a relative intention worth honouring. **And the terminal stopped repainting every three seconds.** Applying the theme was unconditional on every poll, and assigning `options.theme` makes xterm rebuild its colour service and repaint the whole grid — so a panel left open repainted every terminal it had, twenty times a minute, to arrive at the colours already on screen. It happens when something actually changes now. ## 0.41.0 — 2026-08-19 15:50 PDT **The sidebar shows your work again.** Past conversations were listed under every folder and they had quietly taken the place over: measured on a real install, **285 of them against 2 running sessions**, with 163 more than a fortnight old. At that ratio a sidebar is not a view of what is happening, it is a haystack with your work in it. They are **off by default** now, and capped at 14 days when switched on, so the list cannot grow without limit again. Nothing is lost either way — `Ctrl+K` then `~` searches every transcript however old, and the empty pane still offers the last few to pick up. Those are places built for looking something up; the sidebar is for seeing what is running. **Right-clicking one does something.** They had no menu at all, so a right-click produced the *browser's* — which does not read as "nothing happened", it reads as the panel not being in charge of its own sidebar. Resume it, copy its directory, or turn the whole listing off. Nothing offers to delete one: a past conversation is a transcript another tool wrote, and deleting someone else's data is not this program's business. **Any setting that was not a checkbox was being stored as one.** `update_settings` ended in a catch-all of `bool(value)`, on the assumption that anything without an explicit branch was a toggle. That held until a setting was a number, at which point 14 was silently stored as `True` — and it would have done the same to the next one. Values are now coerced to the shape of their own default, which is the schema and was there all along. ## 0.40.0 — 2026-08-19 15:42 PDT **Right-click works on sidebar rows again.** The preview popup added in 0.34.0 sat at a higher layer than the context menu, and hovering a row for half a second is exactly what you do on the way to right-clicking it — so the menu opened *behind* the preview, every time, on any session you were not already looking at. It failed silently, because the clicks still landed on a menu you could not see. The preview now sits below the menu and closes when one opens, and the test suite asserts the order of the whole overlay stack. **CLIque can make the directory.** Typing a path that does not exist ended in "there is no directory at that path" and an instruction to go and find a shell — from a tool whose entire job is running shells. There is a **Create it** button on that message now. Parents are created; a relative path, an empty one, or something that exists and is not a directory is refused with the reason. Never implicit: it happens because the button naming that path was pressed. ## 0.39.0 — 2026-08-19 15:26 PDT **Move a session to another folder without dragging it.** Filing a session somewhere else was drag-and-drop and nothing else — which does not exist on a phone, and is not discoverable on a desktop either, since nothing about a row says it can be dragged. Right-click a session (long-press on touch) and there is now **Move to folder…** and **Take out of its folder**. Worth saying plainly, because the two get conflated: a session's **folder** is a label and can change whenever you like. Its **working directory** cannot — the CLI is already running there, and no process can be moved to a different one. If a session is in the wrong directory, the answer is a new session in the right one. ## 0.38.0 — 2026-08-19 15:17 PDT **The overlapping text in menus is fixed, and it was not the renderer.** Two cursors, a choice repeated on the line below it, fragments of one line sitting inside another — reliably visible whenever a CLI drew a list to pick from. The cause was the order of two calls on attach. A session is created far wider than any browser — 236 columns against a typical 100 — and the window was resized to the browser's size *after* the terminal was attached. So tmux painted a complete frame at 236 columns into a terminal that wraps at 100, the CLI then received the resize and redrew, and the correct frame landed on top of the wreckage of the first one. The window is sized before anything is painted now, and the scrollback is captured at the width it will be shown at. Two suspects were ruled out with measurements before the real one was found: the Unicode 11 width tables agree with the C library tmux uses on all 26 characters these CLIs actually draw, wide ones included, and the pane width does not drift when a second client attaches. Neither was it. **Renaming a session no longer saves halfway through.** The sidebar is rebuilt on every poll and an inline rename puts a live text box inside it, so the rebuild removed the box mid-word — and removing a focused element fires the event that commits the rename. Typing a name got cut off and saved every three seconds. The list now holds still while a name is being typed into it. ## 0.37.0 — 2026-08-19 15:11 PDT **Restarting CLIque no longer kills every session.** This is the important one. The systemd unit did not set `KillMode`, so it took systemd's default — `control-group`, which signals *every* process in the unit's cgroup on stop. The tmux server CLIque starts is a child of the panel, so it lives in that cgroup: `systemctl restart clique`, an upgrade, or a reboot silently destroyed the sessions this tool exists to keep alive. Sessions outliving the browser is the promise; they have to outlive the panel too. `KillMode=process` is now in the unit, the test suite fails without it, and a session and its scrollback are verified to survive a restart. **If you installed the unit before this release, copy `deploy/clique.service` over your `~/.config/systemd/user/clique.service` and run `systemctl --user daemon-reload` — editing the repo file alone changes nothing.** **The working indicator stops spinning forever.** tmux's activity clock counts a *redraw* as output, so a CLI that animates while it waits ticks it endlessly and sat permanently on "working". Measured on a real Grok CLI left completely alone: the clock ticked every two seconds for as long as it was watched, while the captured pane stayed byte-identical the entire time. The clock still decides cheaply. Once a pane has claimed to be busy for eight seconds it has to prove it: the visible screen is captured and compared, and text that has not changed for four seconds is not work whatever the clock says. Quiet panes are never captured, so the ordinary case costs nothing, and eight sessions all stuck in the pathological state cost under two per cent of one core. The trade, stated plainly: a CLI that is genuinely busy while redrawing a *byte-identical* screen will read as idle. A spinner is not that, and neither is streamed output. A session that has just started also no longer fires a "finished" notification as it settles. **Peek shows what was said, not what was drawn.** A modern CLI's pane is mostly frame — box rules, separators, an input box around nothing — and eight raw lines of that buried the one line that answered the question. Lines made entirely of box-drawing glyphs, rules, prompt marks or whitespace are dropped, which is a property of the text rather than knowledge of any CLI; a line with one real word in it is always kept. A wider window of scrollback is searched so the filtering has something to find. ## 0.36.0 — 2026-08-19 14:54 PDT **Text no longer smears when you have a selection.** Fragments of old lines were being left behind when a screen redrew underneath a live selection — an interactive menu being arrowed through was the reliable way to see it. Not a font problem and not a character-width problem: xterm's default DOM renderer draws elements per run of text, and those are the stale nodes. The canvas renderer, now vendored, repaints the whole cell grid each frame, so there is nothing left over to see. Canvas rather than WebGL deliberately. WebGL is faster and it wants a GPU context that a phone, a VM or a remote session can refuse or lose, and this panel is meant to open anywhere. If the file is missing or a 2d context is refused, the DOM renderer is still there — imperfect beats absent. **The directory picker is a picker.** It was a type-ahead list, and browsers filter those by whatever is already in the field — which is prefilled with the directory you are in, so it showed exactly one entry and looked like CLIque knew nothing. There is a real dropdown beside the field now, grouped into *Running now*, *Recent* and *From history*, showing everything regardless of what is typed. Type-ahead still works once you start typing somewhere new. **A path that is not there says so before you press Start.** It used to fall through to the server's error, which is correct and arrives after you have named the session, chosen a CLI and committed. It also has an explanation worth giving: the picker offers directories from past conversations, and a project that has since moved is still in that history. ## 0.35.0 — 2026-08-19 14:45 PDT **Stop typing paths from memory.** The working directory field in the new session dialog now suggests everywhere CLIque already knows you work: what is running right now, then what you looked at most recently, then the directories your past conversations came from. It is a native suggestion list, so it is type-ahead on a desktop and a proper list on a phone keyboard — and typing somewhere it has never heard of still works, which a dropdown would have taken away. The field also stops defaulting to `/root`. That was the machine this was written on rather than anybody's default; it is now where you were last, or the home of whoever started the server. **And it tells you what is already happening there.** Two agents in one directory is the cheap mistake with the expensive recovery — both editing the same files, neither aware of the other, and an afternoon spent working out which change came from where. Before you start, the dialog says so: > *api rewrite is already running here · 13 files were written in the last 15 > minutes · 2 uncommitted changes on main* Advisory only. No locks, no forced worktrees, nothing refused — somebody starting a second session in a busy folder usually means to, they just did not know. Uncommitted changes are only mentioned alongside something else, because on their own they are the normal state of every repo anyone works in. It reads exactly three things: CLIque's own session list, file timestamps, and git where a repo happens to be — the list the product is allowed to read. A directory that is not a repo simply reports less. Pulled when you stop typing a path, never polled, so an idle panel still costs what it always did. Available to scripts as `GET /api/workspace?cwd=…`. ## 0.34.0 — 2026-08-19 14:40 PDT **Which one actually needs you — one answer, not twenty indicators.** The sidebar is honest and it does not scale: at twenty sessions, reading every ring and deciding is a job, and it is a job you do every few minutes. CLIque now ranks the same facts and names one. `Ctrl+K` puts it first among the commands whenever anything is blocked, and it is the top block on the empty pane, which is the screen you land on after being away. The order is error, then waiting, then unread — and among equals, whichever has been like that longest, because the one blocked for eleven minutes is costing more than the one blocked for ten seconds. A working session never appears; it does not need you, and listing it would teach you to ignore the list. It is a sort, not a model. Every fact in it already existed — the attention tiers, the activity clock, the unread mark — and nothing new is captured, polled or inferred to produce it. **Peek at a session without opening it.** Hover a row in the sidebar and the last few lines appear beside it; on a phone, long-press and choose "Peek at the last lines". The ring tells you a session is waiting, and this tells you what it is waiting *for* — which used to mean opening the tab, reading, and going back to what you were doing. Nothing is captured until you actually ask. There is no poller behind it, a sidebar of twenty sessions costs nothing until a pointer settles on a row, and the answer is cached against the pane's own activity clock so moving back and forth across a quiet sidebar is one capture rather than one a pass. Available to scripts as `GET /api/sessions//peek`. **Front-end logic is tested now.** `app.js` has no build step and no module system — the point — and that also meant its decisions had nowhere to be tested from. `tools/frontend_check.js` reads the file, cuts out a named region, and runs it against stubs. No jsdom, no bundler, no `package.json`. The ranking above ships with eight assertions covering what must never appear in it. ## 0.33.0 — 2026-08-19 14:32 PDT **Move a half-typed thought to the session it belongs in.** You are partway through an instruction and realise it is the *other* agent that should get it. That has always cost four steps — select, cut, switch, paste — to move text the panel was already holding on the server. One step now: the **›** beside the prompt box, `Ctrl+K` → "Move this draft to another session", or drag that control onto any row in the sidebar. - It is a move, not a copy. Two sessions holding the same half-written instruction is a way to send it twice. - Text already waiting in the target is never overwritten — the arriving draft is added underneath it, with a blank line between two thoughts. - The cursor lands at the end of the sentence you were in. Drafts already survived tab switches, reloads and a closed laptop, because a draft lives on the session rather than in the box. This only changes which session that is. **The command palette can be borrowed as a picker.** "Choose a session" is now the same list, the same typing and the same arrow keys as jumping to one, with the actions taken out and a callback put in — rather than a second list widget to search, style and keep working on a phone. Moving a draft is the first thing that uses it. ## 0.32.0 — 2026-08-19 14:23 PDT **One prompt box, not two.** Claude, Codex, Gemini and most of the rest draw their own input box at the bottom of the pane, and CLIque was putting a second one directly underneath it. On the new `auto` setting — the default — the panel does not draw a box for a CLI that already has one. A shell draws no box, and there the panel's stays, because it is not a duplicate of anything: it is the only place Run, the repeat counter and a saved draft exist at all. Which CLIs have their own box is `own_input = true` in `clis.toml`, so it is a line of config for a CLI we have never heard of rather than a code change. The mode pill no longer goes with it — and it used to. Anyone who turned the old setting to "terminal" to stop the double box also lost the control for Claude's permission mode, with nothing saying why. The pill is what the bar carries now when the box is gone. **Real icons.** The panel drew its controls with Unicode characters — `✕`, `⚙`, `✎`, `▸`, `⇩` — which is a set only by accident: each one comes from whatever font the operating system picks, so the weights disagreed, the sizes disagreed, and several were missing outright on a phone. Twelve Lucide icons are now vendored into a single inline sprite: about 4 KB, no request, no font, no build step, and `currentColor` so every theme recolours them for free. `tools/build_icons.py` regenerates it and the test suite fails if the page and the icon list disagree. ## 0.31.0 — 2026-08-19 14:14 PDT **Somebody else's outage, said in your panel.** A CLI that has gone quiet and a provider that is down look identical from the outside, and the difference is whether you spend twenty minutes debugging your own prompt. CLIque now reads the public status page of the provider behind each CLI you have **running** and puts one line under the tabs when it is not good news, with a link to the page. - Nothing is drawn when everything is fine. "All systems operational" is not news, and a bar that is always there is a bar nobody reads on the day it finally says something. - Claude, Codex, Copilot and Cursor ship with a feed. Any other CLI is two lines of TOML — `status = { url = "…/api/v2/status.json", page = "…" }` — because adding a CLI has never been allowed to need a code change. - One format, Atlassian Statuspage. A second parser here would be the first step towards a directory of per-vendor scrapers. - Read every five minutes, only for CLIs with a session open, and not at all when the panel is idle. No identifier, no session name, no query string. This is the only request CLIque makes on its own; `service_status: false` stops the thread immediately and the panel never reaches the internet. **Overlapping characters in a status line are fixed.** xterm.js ships Unicode 6 width tables, in which an emoji-presentation glyph such as `⚠️` is one cell wide. Fonts draw it as two, so the terminal reserved one column, the glyph painted two, and the next character landed on top of it — which is why a Claude Code status line looked like it was printing over itself. The Unicode 11 addon is vendored and active. Nothing was wrong with the CLI. **tmux's own status bar no longer shows through.** It was switched off globally, and that was not enough: this server reads your `~/.tmux.conf` on purpose so your keybindings work, and a config with a status-line plugin turns it back on per session. The result was CLIque's plumbing on screen — a viewer session announcing itself as `sm-view-ba434f` at the bottom of a pane. Now set on each session it creates, which beats the global value without costing you anything else you had configured. **The changelog tab works.** It fetched an absolute path, which escapes the `` and resolves against the site root — so it worked on `127.0.0.1:3200` and 404ed for everyone reaching the panel through `tailscale serve` at `/clique`, which is the documented way to run it. A test now fails the build on any API call that escapes the mount point. **Reconnecting no longer writes into your scrollback.** "— disconnected, retrying —" was printed into the terminal itself, where it is permanent, cannot be scrolled past, and is indistinguishable from something your program printed; a restart during an evening's work left a screen of nothing else with the real output pushed off the top. Connection state is about the pane, not from it, so it is an overlay that clears itself. The retry behind it was also fixed: it backed off not at all and never stopped, so a laptop shut overnight woke to twelve panes that had each tried twenty thousand times. It now doubles from one second to thirty, gives up after an hour, and stops immediately when the session is known to be gone. **The panel says when the server was upgraded under it.** A panel left open for days against a server that has been updated is a browser running last week's scripts, and every symptom of that looks like a bug somewhere else. It says so once, with a Reload button, and never reloads on its own. Also fixed, from a review of the front end and the storage layer: - **A folder colour was written into a `style` attribute unchecked**, and the server never validated it — so recolouring a folder could put script on the next person's screen. Hex only now, refused at the setter and refused again at the point of drawing, because a state file written by hand never passes through the setter. - **`POST` and `PATCH /api/folders` return the folder**, not `{"ok": true}`. A caller whose colour was rejected had no way to find out. - **Two background loops could double up.** Turning the webhook off and on again — or the new status feed — cleared the stop signal out from under a thread that was still parked on it, leaving two watchers running and every notification arriving twice. Each loop now retires when it is no longer the current one. - **Two maps grew forever.** Sessions that had finished, and sessions marked for attention, were keyed by id and never removed, in a panel meant to stay open for weeks. - `node --check` now runs on the front end in the test suite. There is no build step, which is the point — and it also meant nothing stood between a typo and a panel that loads and then does nothing. ## 0.30.0 — 2026-08-19 13:49 PDT **A security pass, from three independent reviews.** Nothing here was reported by a user; all of it came from reading the code with fresh eyes. Upgrade if you expose CLIque beyond your own machine. - **The `Origin` check could be defeated by anyone with a free tunnel.** It fell back to the DNS-rebinding allowlist, which accepts any `ts.net`, `trycloudflare.com` or `ngrok` host — correct for the `Host` header, and the exact opposite of what is wanted for `Origin`. A page on someone else's ngrok subdomain counted as our own page, which is the cross-site WebSocket attack the check exists to stop. `Origin` must now match the `Host`, or a name you configured. - **`/api/state` returned `webhook_secret` to any read-only token** — enough to forge a signature your receiver trusts. It is write-only now: the page is told whether one is set, never what it is. A blank field means "leave it alone"; type a single `-` to remove one. - **`/brand/../app.js` served the application before login.** The public-asset test was a `startswith`, and URL parsing does not collapse `..`. It resolves the path and checks containment now. - **The webhook's link-local refusal had two holes**: redirects were followed after the check had passed, and `::ffff:169.254.169.254` is not "in" the IPv4 network so the comparison waved it through. Redirects are refused outright — a webhook receiver has no reason to redirect — and mapped addresses are unwrapped before testing. - **`state.json` was world-readable** while the password, signing secret and token store were all `0600`. It holds the webhook secret and every unsent draft. Now `0600` from creation, and tightened on load for files written by earlier versions. - **The login form read an unbounded body** — the one request an unauthenticated caller may send. Capped before the read. - **Control frames were accepted up to 8 MB.** RFC 6455 allows 125 bytes and no fragmentation; a socket with no write permission could send oversized pings indefinitely. - **New sessions defaulted to `/root`**, which was a leftover from the machine this was written on rather than a product default. It is now the home of whoever started the server. Also fixed: a write to the PTY was one-shot and silently truncated a large paste; `tmux.exists` let a timeout escape into a request handler; the token store had no lock while being written from three threads; and a `Content-Length` that was not a number returned 500 instead of ignoring it. Every one of these is now covered by the test suite, which is the only way a fix stays fixed. ## 0.29.1 — 2026-08-19 13:06 PDT **You can tell which session you are in from the sidebar again.** Three things were wrong at once, and the third hid the first two. The active row's highlight was a background colour and nothing else, which is too quiet against a dark theme when it is one row in thirty. It now takes the active CLI's colour down its left edge — the same language the tab bar and the pane border already speak. That edge was reading a custom property set on `
`, and the sidebar is a *sibling* of `
`, so it never saw it. Both properties are set at the root now. They are also two properties rather than one, because "which CLI is this" and "which row am I on" are different questions: turning the CLI tint off should not take the selection highlight with it. And the case that prompted this: **a collapsed folder draws no rows at all**, so switching to a session inside one left the whole sidebar looking like nothing was selected — because nothing was, there was nothing there to select. A folder holding the active session now says so in its header, open or shut. The sidebar also scrolls a selected row into view when it is off-screen, without animating, because a list someone is reading should not slide under them. ## 0.29.0 — 2026-08-19 13:04 PDT **Fixed: pasting text did nothing if the clipboard also held an image.** Copying from a browser, a spreadsheet, or most document editors puts a rendered image on the clipboard *alongside* the text. The screenshot handler claimed the paste whenever an image was present, so those pastes quietly became a file on disk and the text never arrived — which from where you are standing is simply "paste is broken". Text now wins whenever both are there. A real screenshot carries no text, so the feature that was actually wanted loses nothing. **Security lint is on permanently.** `ruff` now runs flake8-bandit over the whole tree as part of the build. Everything it flagged is either fixed or annotated at the line with the reason it is not a finding — running `tmux` from `PATH` is the product, and the SHA-1 in the WebSocket handshake is mandated by RFC 6455. Two real fixes came out of it: - **The webhook could be pointed at cloud metadata.** "POST to a URL someone typed" is the exact shape that leaks instance credentials on a cloud box. Link-local addresses are now refused, checked after resolving the host, and re-checked at the moment of sending rather than only when the setting is saved. Loopback and private ranges are deliberately still allowed: ntfy on `127.0.0.1` or Gotify on the LAN is the normal case, and breaking it to protect an admin from their own machine would be the wrong trade. - **An `assert` was doing real work** in the PTY reader thread. Asserts vanish under `python -O`, and what replaced it was an `OSError` raised in a daemon thread with nobody to catch it. **Stopped committing build artefacts.** `pip wheel .` leaves a `build/` directory in the working tree, and sixty generated files had been swept into the repo — a stale duplicate of every module sitting beside the real one. **A clock you can read.** 12-hour or 24-hour, in Settings → Notifications. Not taken from the locale, because plenty of people read one format at work and the other at home. **And a star button** on the About pane. A star is the whole marketing budget — the lists that catalogue tools like this one sort by exactly that number. ## 0.28.3 — 2026-08-19 12:56 PDT **The version in the corner tells you when there is something new**, and takes you to it. A changelog nobody opens is a file, not a feature — so the running version grows a small dot when it is not the release whose notes were last read, and clicking goes straight to them. Seeded on first load, so a fresh install does not arrive already claiming to have news. **`CPU`, `MEM`, `SWAP`, `DISK`, `LOAD`** — the stat labels were lower case, and four of the five are acronyms, so it read as wrong before it read as anything. The values keep their own case: "138.1G free" is a sentence, not a label. **Fixed: the test suite pointed at a file that no longer exists.** `tools/smoke.py` named `config/clis.toml` by hand, and when the catalogue moved into the package in 0.28.0 it kept passing locally against a stale copy left on disk while CI — which has no stale copy — failed. It now asks the same function the app asks, so there is one answer and it cannot rot again. That is the better lesson: a path written down twice is a path that will disagree with itself. ## 0.28.2 — 2026-08-19 12:45 PDT **A clock, a tip, and the notification link actually works.** The empty pane gained the time — in the browser's own zone, or one you name in Settings → Notifications. `Intl` already carries the whole zone database, so this needs no data and reaches nothing; a name that is not a real zone is refused when you save it rather than throwing in the page. And one line of advice, keyed to the date so it is the same all day: a tip that changes on every repaint is a slot machine, and nobody finishes reading one. **The fix that mattered.** Every webhook payload since 0.27.0 has carried a `?session=` link back to the exact session — and the panel was ignoring the parameter entirely, so tapping a notification dropped you on whatever tab you had left open. Which is the one thing a notification exists to save you from. It now opens and selects that session after the workspace restores, then strips the parameter so a reload does not keep dragging you back. ## 0.28.1 — 2026-08-19 12:41 PDT **The empty pane does something now.** It used to say "No session open", which is the one fact an empty pane already demonstrates. That space is where you land after finishing something and where you arrive after signing in, so it is worth more than a label. It now shows what the box is doing — how many sessions are running, how many are working, and how many are waiting on you, that last one in the colour that means it — and then the two shortest routes back in: the sessions you looked at most recently, and conversations you can resume. One click each. All of it is state the panel already polls. No new endpoint, nothing to configure, and nothing fetched from anywhere: an empty pane is not the place to start having a self-hosted tool phone out for a weather icon. ## 0.28.0 — 2026-08-19 12:37 PDT **It installs with one command now.** `pip install clique`, and the packaging finally ships the thing people came for. The wheel used to carry the server and none of the interface — every static file, the vendored xterm.js, the icons, and the CLI catalogue were all outside the package, so an installed copy would have started and served 404s. The catalogue moved to `clique/config/clis.toml` and the web assets are declared as package data. Verified the only way that means anything: built the wheel, installed it into a clean virtualenv, started it, and loaded the page from site-packages. The version is read from `clique/__init__.py` now, so there is one number to bump and the wheel cannot disagree with what the panel reports about itself. Where the CLI catalogue is read from, in order and with no magic: `--config` if given, then `$CLIQUE_HOME/clis.toml` if it exists, then the copy in the package — which in a checkout is the repo's own file, so editing it works exactly as it always did. `clique config` writes an editable copy into `$CLIQUE_HOME` for an installed copy, and refuses to do it in a checkout, where it would put a shadow file in front of the repo's and make the next edit appear to do nothing. **The collapsed rail keeps your markers.** It drew a plain dot regardless of what you had chosen, so shrinking the sidebar quietly threw away both the CLI icons and the status rings — the two marks that make a column of sessions readable at all. It now makes the same two calls a sidebar row makes, so whichever of them your settings put in charge is what appears there too. ## 0.27.1 — 2026-08-19 12:29 PDT **URLs in the pane are clickable.** A click opens a new tab; `Ctrl`/`Cmd` and a click opens a new window. Written rather than vendored: xterm already exposes the API this needs, the whole thing is forty lines, and one more vendored file is one more version to keep in step with the core for a feature this size. `http` and `https` only, checked when matching and again when opening. A terminal prints whatever a program sends it, so what is on screen is not trustworthy input — `javascript:` and `file:` are the two that would matter, and neither is matched or opened. Trailing punctuation is trimmed because it belongs to the sentence, but brackets only when unbalanced, so a Wikipedia URL ending in `)` still works. ## 0.27.0 — 2026-08-19 12:26 PDT **It can reach you with the panel shut.** An attention state you only see while looking at the panel is decoration. One field in Settings → Notifications takes a URL, and a small JSON body is POSTed there when a session starts waiting, stops on an error, finishes, or dies. One URL, not a list of services. ntfy, Gotify, Discord, Mattermost, Home Assistant and Uptime Kuma's push monitors are all "POST some JSON to this address", so a single field reaches every one of them with no app, no account, no SDK and no settings sheet full of logos. Adding the second integration is the decision that creates a permanent "please add mine" queue, so there is no first one. Not Web Push either: VAPID needs signing the standard library cannot do, which would cost a dependency — and ntfy's own app already delivers real push to a phone for free. Each event fires on the edge. A session that has been waiting an hour is not news every ten seconds, and a notifier that repeats itself is one you mute. An optional shared secret signs the exact bytes sent. One attempt, five second timeout, no retry queue — a dropped notification is superseded by the next change, and durable retries mean a database. **And a test button**, because every webhook UI needs one for the same reason: you paste a URL and want to know it works now, rather than the next time something finishes at three in the morning. The watcher only exists while a URL is set. With no webhook configured nothing runs, and an idle CLIque costs exactly what it did before. ## 0.26.0 — 2026-08-19 12:19 PDT **Waiting on you, and stopped on an error, are now different from idle.** The question this whole thing exists to answer is *which of these needs me*, and until now a session waiting for an answer looked exactly like one that had finished. The activity clock cannot tell them apart; it only knows output stopped. Three tiers, each optional, each falling back to the one below: 1. **The activity clock** — shipped, works for any CLI, says only working or quiet. 2. **Patterns over the pane** — regexes declared per CLI in `clis.toml`, matched against the last forty lines once a session goes quiet. A CLI with no patterns skips the tier. Generic defaults ship for Claude Code and are meant to be edited: these are the prompts most CLIs show, not a transcription of anyone's wording. 3. **The session says so itself** — `POST /api/sessions//attention` with `waiting`, `error` or `clear`. Wire it to a hook your CLI already has and it is exact rather than guessed. Note what is not here: any knowledge of a vendor. Tier 2 reads config you can edit the day a prompt changes, without waiting for a release. Tier 3 receives an assertion and believes it. The line is that patterns stay strings in a TOML file. A signal goes stale by itself once output arrives after it. A session that carried on is not waiting, and a mark that sticks when it should not is a mark you learn to ignore. **Also: the wrong clock.** `busy` and unread were reading tmux's `session_activity`, which moves when a *client* does something and stands still while a detached session produces output for an hour. `window_activity` is the one that tracks the pane. Both are read now and the later wins — so a session with no browser attached finally looks busy while it is, and goes unread when it should. **And the pressure dots are legible.** CPU, memory, load and disk were on a smooth green-to-red hue ramp, which looks right in a screenshot and tells you nothing in use: the middle of that ramp is one indeterminate yellow, and 40% and 55% are the same colour to anyone not holding a swatch. Four hard bands now — calm, busy, high, critical — with critical pulsing, because at the point where something is about to go wrong the dot should find you rather than wait to be looked at. ## 0.25.0 — 2026-08-19 11:53 PDT **Which CLI am I in?** Nine panes of black text look identical, and the moment it matters is the moment after you switch — a Claude prompt typed into a shell is a mistake you only notice once it has run. The top edge of the pane, the top of the active tab, and the prompt box while you are typing in it all take the active CLI's colour, and switching tabs repaints them. One custom property set in one place, so turning it off in Settings → CLI markers is one assignment rather than a hunt through a stylesheet. The colours are yours to change now, per CLI, with a reset back to whatever `clis.toml` ships. A palette that reads well on the built-in dark theme can vanish on somebody's Solarized, and that was never a reason to make anyone live with it. They save on the server with everything else, so a colour chosen at the desk is already there on the phone. ## 0.24.0 — 2026-08-19 11:21 PDT **The logo stays the logo. The ring around it says how the session is doing.** Status used to be painted onto the CLI's own mark: Claude's icon rendered in whatever colour the session happened to be, and a busy one faded in and out. That spent the one thing on a row you could identify without reading — and it was reporting whether a browser was attached, which is not what anyone wants to know. Four states now, and only three of them draw anything: - **working** — an arc turning around the icon - **waiting** — finished and not looked at yet: a steady ring, pulsing - **idle** — alive and quiet, and it draws *nothing*. Most sessions are fine most of the time, and twenty marks all saying "fine" is twenty things to look past on the way to the one that is not. - **stopped** — the mark goes grey and the ring goes away Only rotation and opacity animate, so the whole thing is composited and costs nothing; `prefers-reduced-motion` keeps every state distinguishable and stops the movement. Each ring carries its own label for a screen reader and a tooltip, because a signal only sighted people receive is half a signal. "Working" is held for a few seconds after the last output. The server reports output in the last two seconds and the panel asks every three, so a CLI writing in bursts used to flicker — and an indicator that blinks twice a second is worse than none. ## 0.23.0 — 2026-08-19 11:17 PDT **An agent takes a screenshot; now you can look at it.** Pasting has worked in one direction for a while: `Ctrl`/`Cmd`+`V` puts an image in the session's own directory and hands the CLI the path, because a path is the only thing that can cross between a browser holding bytes and a terminal that cannot draw a picture. Nothing carried the answer back. An agent that screenshotted a page had produced something you had to leave the panel to see. Sessions that made an image grow a count in the tab bar. Clicking it opens the grid, clicking a thumbnail opens it full size, and **Send path** drops the file back where you are typing — the same landing paste already uses, so the round trip is one gesture each way. Deliberately not a file browser. It lists images that appeared in the working directory *while the session was running*, one level deep, in directories you choose (Settings → Images). That rule is what separates what the agent made from what the project already contained, and it is filesystem state — no vendor is asked what happened, and no agent has to be told this exists. Everything the browser sends is re-derived server-side: a path that climbs out of the working directory is refused after symlink resolution, and the `Content-Type` comes from the file's magic bytes, never its name. ## 0.22.0 — 2026-08-19 10:55 PDT **Long press a session on a phone and the menu opens.** Rename, archive, move and kill were reachable only by right-clicking, which does not exist on touch. Folders got away with it — the pencil is the same menu with a way to find it — and tabs had the gear. Sidebar rows had nothing, so half the app was missing on the device most likely to be checking a session from the sofa. - A **half-second press** on any session row opens the same menu right-click opens. A short buzz when it lands, because nothing has moved on screen yet and a press that has taken is otherwise indistinguishable from one that has not. - **Moving cancels it.** A press that turns into a scroll is a scroll, with enough slop that a resting finger does not count as movement — a menu that refuses to open is worse than one that opens when you meant to scroll. - Lifting after the menu opens no longer counts as a tap on the row, which would have opened the session behind its own menu. - Menu rows get a **real tap target** on any coarse-pointer device, rather than the padding a mouse cursor is happy with. Delegated to the sidebar rather than bound per row: the tree is rebuilt on every poll, and three listeners per session every three seconds is churn for nothing. ## 0.21.1 — 2026-08-19 10:50 PDT **`API.md` exists.** The settings sheet had been pointing at a full reference for several releases and there was no such file — the worst version of a documented API, because it claims a surface nobody can find. - Every route, every settings key, every field `PATCH` accepts, what a read-only token is refused, and exactly what `/healthz` will and will not tell a caller with no credential. - **`tools/api_drift.py` keeps it honest**, alongside the other suites: add a route or a setting without writing it down and it fails. A reference maintained by hand holds for about three releases; this one cannot quietly fall behind. Nothing changed in the app. The rule behind it is in `CLAUDE.md` now — every action in the panel is an HTTP call, because a feature reachable only by clicking is a feature an agent driving CLIque cannot use. ## 0.21.0 — 2026-08-19 10:43 PDT **Your tabs are yours, not your browser's.** Which sessions had a tab, their order, and which groups were collapsed lived in `localStorage` — the one place a person's choices were still stranded on one machine. Close the laptop and open the panel on the phone and it was a blank workspace; clear the browser and twelve panes were a morning to reopen. - **The workspace is on the server now**: open tabs and their order, the tab that was in front, and the collapsed state of Running, Ungrouped and Archived. Reload, or sign in somewhere else, and the panes are where you left them. - **Lifted, not reset.** Whatever the browser was holding on the day this changed is read once, moved up, and removed locally. Nobody loses the tabs they had open. - Restored on the first poll and never re-applied after, so two panels open at once do not drag each other's tabs around mid-read. Last one to touch a tab wins the stored copy — the right answer for one person on two devices. Sidebar width and sidebar shown/hidden stay in the browser, and stay the counter-example: those are about the screen in front of you, and a phone should not inherit a 400px sidebar from a desktop. ## 0.20.0 — 2026-08-19 10:30 PDT **The changelog is in the app now, and it finally says what time it was.** - **Settings → Changelog** lists every release, newest first, read from the same `CHANGELOG.md` this repo ships. One copy, so the notes in the app cannot drift from the notes on disk — which is what happens to every second copy of a release note by about the third release. - **Every entry carries a time, in Pacific.** Nineteen releases had shipped on a single date, which made the date worthless for telling them apart or putting them in order. Backfilled from the commit times, converted out of UTC, so the evening ones correctly say the evening they happened rather than the next morning in Greenwich. - The day is a heading and the entry is a time under it, and the release you are actually running is marked. - **`GET /healthz`, no login required.** Uptime Kuma, Gatus, Healthchecks and every other self-hosted monitor want one URL that returns 200, and anything that makes them carry a credential first is a thing that ends up unmonitored. Anonymously it returns `{"ok": true}` and nothing else — no version, no session names, no counts. Signed in, or with a token, it adds uptime, how many sessions exist and how many are alive, connected terminals, and whether tmux is reachable at all. Fetched the first time the tab is opened rather than at load — release notes are read twice a year and the panel opens in a quarter of a second. Markdown is parsed on the server into structure rather than markup, so the browser builds elements instead of assigning HTML, and nothing out of a file can turn into a node by accident. ## 0.19.0 — 2026-08-19 10:20 PDT **What you have not seen, and where you stopped reading.** - **An unread dot** on any session that produced output while you were elsewhere, in the sidebar and on its tab. Flashing says *something happened* and is gone a second later; this is still there an hour later, which is the point of it. Looking at the pane clears it. - **A rule across the pane** at the point you left, so coming back to four hundred new lines shows where your eye stopped. Neither stores anything new. The dot compares tmux's own activity clock against the `last_seen` already written on every tab switch, and the rule is a terminal decoration rather than text written into the buffer — writing into a pane that a full-screen CLI is repainting would garble whatever it was drawing. ## 0.18.0 — 2026-08-19 10:19 PDT **Closing a tab has always kept the session running. Nothing ever said so.** The ✕ read as destructive, so people killed sessions they only meant to put down. - Closing now says what it did — *"Closed the tab — is still running"* — and offers **Kill it instead** in the same breath, for the times that was what you meant. Killing still asks first. - **Running is now the sessions with no tab open.** A pane you are looking at is already a tab across the top; a second row for it says nothing. What needed a home is the session still working with nothing on screen — the one you forget you started. It sits at the top of the sidebar. ## 0.17.0 — 2026-08-19 10:05 PDT **A half-typed prompt survives everything now.** Switch tabs, reload the page, close the laptop and open it somewhere else — the words are still in the box. - Drafts are **per session**, so twelve panes can each hold an unsent thought. - They live **on the server**, not in `localStorage`: an unsent instruction is about the work, so it follows you between devices. Sidebar width is the counter-example and stays in the browser. - Written on a debounce rather than per keystroke, and committed immediately when you switch tabs or the page is hidden — the laptop-lid case, which is exactly when someone expects their words to still be there. - Sending clears the draft, because it is no longer unsent. ## 0.16.1 — 2026-08-19 09:59 PDT Marks beside each support option — a coin glyph in each project's colours, and a cup for Buy Me a Coffee. Drawn inline rather than fetched: no build step, no CDN, and nothing to 404 on a box with no route to the internet. They are our own glyphs rather than the projects' official logos, which keeps someone else's trademarked artwork out of a repo that is about to go public. ## 0.16.0 — 2026-08-19 09:56 PDT **A tip jar, in About and on the repo page.** CLIque is free and staying that way; this is for anyone who wants to say thanks. - Buy Me a Coffee, plus BTC, SHIB and DOGE addresses. - **Addresses are shown in full and wrap** rather than truncating, with one click to copy. A half-shown chain address is worse than none at all, because someone will copy what they can see — and a wrong address does not bounce, it just loses whatever was sent. - The same list is in the README, and `.github/FUNDING.yml` puts the coffee link behind GitHub's own Sponsor button for when the repo goes public. ## 0.15.1 — 2026-08-19 09:50 PDT **The About tab in settings was invisible.** Seven tabs did not fit the sheet, the strip scrolled horizontally, and its scrollbar was hidden by design — so the last tab was off the edge with nothing on screen to suggest it existed. The strip wraps now instead of scrolling. Costs a second row on a narrow sheet, and it is the same fix a phone needs anyway. ## 0.15.0 — 2026-08-19 09:43 PDT **A session you just started is now the first thing you see.** It was being filed into a folder automatically by its directory, which meant a new session could appear anywhere in the tree — or, if a folder rule matched, somewhere you were not looking at all. - New sessions land in **Ungrouped** unless you pick a folder when you create them. Filing is a decision made afterwards, if at all. - **Ungrouped now sits above the folders**, not below them. - Auto-filing by directory stays where it earns its keep — **adoption**, where there is nobody to ask. **The right-click menu names what is actually there to destroy.** A session whose process ended long ago still offered *Kill*, which asked you to confirm stopping something that had already stopped, and offered nothing for the thing you probably wanted: the row gone. Stopped sessions now offer **Delete**, with wording that says what it does. Running ones are unchanged. ## 0.14.0 — 2026-08-19 09:40 PDT **Every binding, in one list.** Shortcuts existed only in `title` attributes, which meant they existed only for whoever already knew they were there. - `?` in the tab bar, `Ctrl`/`Cmd`+`Shift`+`/`, or **Keyboard shortcuts** in the palette. - Grouped by what you are doing: getting around, inside the palette, reading a pane, working in a session. - Written from one table in the source, so a binding that ships without a line here is a visible omission rather than a silent one. - It says where a key is not ours to promise: the autonomy-mode key is whatever that CLI declares in the registry, so it differs between them. ## 0.13.0 — 2026-08-19 09:35 PDT **The view no longer moves while you are reading it.** On a busy session, a line arriving mid-sentence dragged the pane to the bottom — and you cannot read, or copy, what will not hold still. Two of the five feature lists ranked this first outright. - **Scroll up and the viewport detaches.** No toggle to find first: the thing you already do in order to read is the thing that stops the yanking. Returning to the bottom re-attaches. - A **badge over the pane** says it is paused and how many lines have arrived since — a detached pane and a dead one otherwise look identical. Click it to catch up. - A lock button in the tab bar, `Ctrl`/`Cmd`+`Shift`+`L`, and a palette entry, for pausing deliberately rather than by scrolling. - Per session, so pausing one pane to read it leaves the other eleven running and following. **The stream is never paused, only the view.** Output keeps arriving and tmux keeps the scrollback; the viewport is put back after each write instead. Freezing the stream would have put the pane out of step with tmux, which holds the real scrollback and does not care what a browser is looking at. ## 0.12.0 — 2026-08-19 09:23 PDT **You can paste a screenshot into a session.** A terminal cannot carry an image, so the only thing that can cross into a pane is text — which meant showing an agent what you were looking at involved saving the file yourself, finding its path, and typing it out. - `Ctrl`/`Cmd`+`V` with an image on the clipboard saves it into the session's own working directory, under `.claude-images/`, and puts the path where you were already typing. Nothing is sent on your behalf: the CLI does not see it until you press enter. - **Text paste is untouched.** The handler only claims the event when the clipboard actually holds an image; everything else goes through to the terminal as before. - It knows nothing about any CLI. Every coding agent can already read a file from a path, so this needed no registry entry and no per-CLI branch. - The path lands in the pane, or in the prompt box if that is where the caret was — or if the pane's socket is down, because a path that silently disappears is worse than one in the wrong place. A toast says which. **What it refuses.** The type is established from the file's own leading bytes, never from a filename or a `Content-Type` the browser supplied — this route writes into a working directory, so what the bytes are has to be settled server-side. PNG, JPEG, GIF, WebP and BMP are recognised; anything else is rejected. Requests are capped at 10 MB, bodies are bounded before they are read, names are random and never overwrite, and the write is contained to `/.claude-images` after symlink resolution. ## 0.11.0 — 2026-08-19 07:33 PDT **The autonomy pill was static.** It showed whatever mode a session started in and never moved again — not when you clicked it, not when you cycled the mode yourself. Clicking sent the key to the CLI and then forgot to write down what it had just done. - Clicking the pill cycles it, and the change is remembered. - **Cycling by hand in the pane moves it too.** The key the registry declares is now watched coming *out* of the keyboard as well as sent *into* the pane, so the pill cannot drift out of step with what you did yourself. The key is passed straight through — this observes it, it does not intercept it. - The label is the CLI's own wording from `mode_label`. It was hardcoded to Claude Code's "(shift+tab to cycle)", which read as a lie on Gemini. - The mode persists, so a reload does not reset it and it follows you to another device. **What the pill knows, and does not.** It tracks the mode CLIque last saw set — by a click, or by the cycle key going through the pane. It does not read the CLI's real state, because that means parsing someone's terminal output, which is the line this project does not cross. Those are the only two ways the mode moves, so in practice they agree. **How to get it for another CLI:** four lines in `config/clis.toml`, no code — documented in the README under *Adding a CLI*. Only Claude Code and Gemini declare modes today. The rest of the registry is a catalogue and none of them is installed here, so nobody has verified which key cycles what, and a pill that names the wrong mode is worse than no pill. ## 0.10.1 — 2026-08-19 07:20 PDT **Fixed: Running and Ungrouped could not be collapsed.** Clicking their headers did nothing at all. Collapsing was written to flip a flag on a folder record, and those two are views over the sessions rather than folders — there is no record to flip, so the click fell through a guard and returned silently. Archived worked only because it had its own one-off boolean. All three now share one mechanism, and it lives in the browser rather than on the server. Same rule as sidebar width: which *folders* you keep shut is about the work and follows you between devices; which *views* you keep shut is about this screen and stays here. Archived still starts closed, since being out of the way is the entire point of it. ## 0.10.0 — 2026-08-19 07:17 PDT **The stats bar is a gauge now.** Every reading carries a dot that moves along a green-to-red ramp as the box works, so a glance says how hard it is breathing without reading five numbers. - Continuous, not three fixed steps. The ramp bends at green→amber over the first 70%, because that is where most of the interesting range lives — a box at 40% and a box at 65% should not be the same shade of "fine". - Deliberately **not** a theme token. Green-to-red is not decoration, it is the one colour convention that means the same thing to everyone, and a theme recolouring it would be repainting the gauge rather than the panel. - Load is graded against core count, disk against how full it is, and swap starts at amber the moment any is in use — "a little swap" is not a healthy reading, it means memory pressure already happened. - The old amber text is gone. The dot says the same thing more precisely, and two marks for one fact is the pattern 0.9.1 removed from sessions. **Folders are easier to work with.** - The collapse triangle is half again as wide and sized to be aimed at, rather than sized to match the label's type. - **A pencil appears on hover** to rename, recolour or delete a folder. Right-click always did this and nothing ever said so; the pencil is the same menu with a way to find it. It holds its space whether or not it is showing, so the counts do not jump sideways as the pointer moves down the tree. - Only real folders get it. Running, Ungrouped and Archived are views over the sessions, not things with a name and a colour. ## 0.9.2 — 2026-08-18 21:53 PDT **Changing the theme now repaints the open terminal immediately.** It was waiting for the next three-second poll — and with the settings sheet still covering the pane you were looking at, that reads as "changing the theme did not change the terminal". Applied on save now: 0.2s instead of up to 3. This is the second half of the terminal-theming problem. The first half was 0.7.0, where every theme was missing the eight *bright* ANSI colours that CLIs lean on hardest — so the background changed and the output did not. Both halves verified in a real browser this time: one pane, no reload, Dracula to Trinity, every colour following. ## 0.9.1 — 2026-08-18 21:32 PDT **One mark per session, and it is the CLI's logo.** Showing a status dot *and* a logo was two marks for one session; the logo now carries the status colour by default and pulses while the session is working. - A logo with its own colours cannot be tinted — used as a mask it flattens to a solid square — so those wear the status as a **ring** instead. Previously they fell back to showing the dot as well, which was the exact thing this was meant to stop. - The plain dot still returns where a session has no marker at all: a CLI set to **None**, or markers turned off. Losing status entirely is the worse trade, and that has not changed. - Turn it off in Settings → CLI markers → Status to go back to a dot beside the logo. **The favicon is the logo again.** It had been drawing only the large chevron, on the grounds that both turn to mud at 16px. True, and beside the point: a tab icon that does not match the one in the window is worse than a slightly busy one. Both chevrons, a third heavier, no tile — the tile's rounding is what was eating the space. ## 0.9.0 — 2026-08-18 21:29 PDT **Your history is in the sidebar, in its folders.** Past conversations now sit under the live sessions in each folder — dimmed, because they answer "where did I leave that" and must never compete with "what is happening now" — with the project and the age, and a click resumes one. 266 conversations, filed by the directory they belong to. - **Repeats are folded.** A scheduled agent writes one transcript per run under an identical opening line; thirty-seven rows reading "You are the unattended responder…" is thirty-seven rows of nothing. The newest is kept and the rest become a ×37 next to it. - **Folder headers count both**, live sessions and the history behind them, so a folder holding two hundred conversations and no running session no longer reads as empty. - Six per folder, then "N more from history". Everything expands at once only when you search. - Titles prefer the CLI's *own* name for a conversation where it wrote one — "Analyze Duchamp room rates emails" beats the first eighty characters somebody typed. Still no summarising: it reads a field that already exists, or the opening line, and stops. - Turn the whole thing off in Settings → Appearance → Sidebar. `Ctrl`+`K` then `~` still searches every one of them. **Tabs reorder by dragging.** A line marks the edge the tab will land against rather than reflowing the bar under the pointer, which is harder to aim at. The order is the browser's, like sidebar width, and rides along with the open-tab list that already survives a reload. **Fixed: the app could open to an empty sidebar and fill in three seconds later.** Python's default listen backlog is five connections, and one page load is well past that — document, stylesheet, three scripts, brand mark, favicon, manifest, `/api/state`, `/api/resumable`, and a WebSocket upgrade. The overflow was dropped. The backlog is 128 now, the opening fetch retries quickly instead of waiting out the poll, and a panel that really is unreachable says so rather than showing an empty app, which from the outside is indistinguishable from a broken one. ## 0.8.0 — 2026-08-18 21:17 PDT **Every past conversation, findable and resumable.** 266 of them on this box, discovered in under half a second. Switching tools should not cost anyone their history. Every CLI worth driving already writes its transcripts to disk, and the registry already knew the argv that resumes one — the only missing piece was finding them. - **Registry-driven, not a Claude branch.** A CLI declares where it keeps its transcripts in `config/clis.toml`: [cli.claude.history] dir = "~/.claude/projects" layout = "dashed-dir" pattern = "*.jsonl" Omit the block and the CLI simply has no history, which is the right answer for `shell`. - **Labelled by the first thing you typed**, read from the head of the transcript and nothing more. Understanding a conversation would be the LLM-summary trap wearing a different hat; the first human turn is free and is what you actually remember it by. - **`~` in the palette** searches them. They stay out of an empty `Ctrl`+`K` on purpose — hundreds of old conversations would drown the twenty sessions you actually switch between — and join the pool as soon as you type. - **Resuming is the same code path as starting.** The registry hands back the resume argv instead of the launch argv, and the new session lands in the folder its directory belongs to. Nothing in CLIque knows what resuming means for any particular CLI. - Discovery is a directory walk plus a bounded read per file, cached, and runs when asked rather than on the three-second poll. **Also:** a new session now files itself into the right folder automatically, the same way an adopted one does. It only did that for adopted sessions. ## 0.7.0 — 2026-08-18 21:11 PDT Four things found by actually looking at the running app rather than at the tests, which all passed throughout. **Adoption now produces sessions you can use.** It filed five live Claude Code sessions as plain shells, named after their directories, in no folder. - The CLI is detected from the pane's **process tree**, not from `pane_current_command`. A CLI started from a wrapper or a login shell is a child of that shell, so the pane honestly reports `bash` — and `bash` is a registered command, so trusting the pane first was wrong every time. - Names come from the tool being replaced, where it recorded them. A sidebar of "mark", "agent-infra", "testcase" is not a migration. - Sessions land in the folder their directory belongs to. - **Adopt is now safe and useful to run twice.** It repairs sessions adopted before detection improved, instead of skipping anything already known. A name you typed yourself is never overwritten — only one CLIque derived. **The terminal no longer opens as a small pane in a sea of dots.** A tmux window has exactly one size, shared by every client on it, and `window-size latest` follows the most recently *active* client — which a browser that has just resized is not. With another tool still attached to an adopted session, that left most of the viewport filled with tmux's dot padding. The browser now sets the window size explicitly, on attach and on every resize. The comment claiming grouped sessions get independent sizes was wrong and has gone. **Themes now reach the terminal properly.** Every theme set a background and the eight base ANSI colours; none of them set the eight *brights*, which CLIs lean on constantly — so a themed panel wrapped output drawn in xterm's default palette. All eight themes now carry the full sixteen, and the built-in dark carries VS Code's Dark+ palette it was always meant to match. **One theme now covers every surface.** White text on the accent, black modal scrims and black shadows were hardcoded, so light themes got a black wash and a pale accent got unreadable text. They are tokens now — and derived from the theme in code rather than added as three more keys every theme must remember, so adding a theme is still one block. The sign-in page follows the browser's light/dark preference: which theme is chosen is not an answer to hand someone who has not signed in. **A new theme: Trinity.** Green phosphor on black. Red stays red and yellow stays yellow — a terminal where an error cannot be told apart from a diff is a costume, not a theme — and everything not carrying meaning leans green. **Also:** brand images, the favicon and the web app manifest are served before sign-in. The login page asked for a password and then tried to draw a logo that was behind that password. ## 0.6.0 — 2026-08-18 20:48 PDT **A real identity.** The brand assets until now were CodemanPanel's, borrowed wholesale — one of them still carried `aria-label="CodemanPanel"`. The mark is two chevrons: a large one with a smaller one tucked into its opening. A prompt, and then a second prompt — many CLIs, one place. Drawn from [the original](docs/brand/original-mark.jpg), traced, and then regularised so the arms are symmetric and the proportions are exact. - Violet `#A855F7` to cyan `#22D3EE` on near-black. Developer tooling is almost uniformly blue; this reads as itself in a crowded tab strip, and both ends stay legible on light and on dark. - **Everything is generated** by `tools/make_brand.py` from one definition of the geometry. The SVGs are written from it and the PNGs are *drawn* from the same numbers rather than rasterised from the SVGs — so there is no renderer to install and no way for vector and raster to drift apart. - Sixteen files: tiled logo, bare mark, monochrome mark, lockup, eight raster sizes, an Apple touch icon, an Android maskable icon, a multi-size `.ico`, and GitHub's 1280×640 social preview. - The 16px icon is not the 512px one shrunk. It drops the tile and the second chevron and thickens the stroke by half, because at that size the full mark is mud and a true-weight stroke is a wire outline. **Installable on a phone.** A web app manifest, `theme-color`, and the iOS meta tags ship with the icons — so CLIque can be added to a home screen and opens standalone. The layout itself is not responsive yet; that is the next piece of the mobile work, and it is on the list. ## 0.5.1 — 2026-08-18 20:35 PDT **Fixed: signing in landed on a white screen.** The Content-Security-Policy added in 0.3.0 shipped as a bare `script-src 'self'`, which blocks *every* inline script. Three of them are load-bearing here — the one that resolves the mount path on the app page, the same on the login page, and the redirect on the page you land on after signing in. All three were silently blocked. The landing page is a blank document whose only content is that script, so a successful login rendered a white screen. Nobody hit it for two releases because nobody signed in: the existing cookie stayed valid across every restart. Renaming the cookie in 0.5.0 forced the first real login since the policy shipped, and the bug surfaced immediately. - Inline scripts now carry a **per-response nonce** that the policy names. Hardening is unchanged — an injected `