CHANGELOG.md
Rama main · réplica de lectura del repositorio original.
# 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 `! <command>` (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 <version>" 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/<id>/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/<id>/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 (`<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/<id>/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/<id>/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/<id>/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/<id>/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
`<base href>` 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 `<main>`, and the sidebar is a
*sibling* of `<main>`, 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=<id>` 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/<id>/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 — <name> 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
`<cwd>/.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 `<script>` still cannot run, because it
cannot guess a nonce minted for that one response.
- Hashes were the alternative and would have been worse: editing a comment
inside one of those scripts would blank the app without a word. That is
close to how this was found.
- The smoke test now asserts that every inline script on both pages carries a
nonce the header actually allows. It could not catch this before, because
curl does not enforce CSP — the suite stayed green while the app served a
blank page.
## 0.5.0 — 2026-08-18 20:29 PDT — **now CLIque**
*Your private clique of CLIs.*
muxpanel described the mechanism; CLIque describes what it is for. The rename
goes all the way through — package, module, socket, data directory, cookie,
environment variables, systemd unit and the tailnet path — rather than
stopping at the visible strings, because a half-rename is worse than either
name.
**Nothing running was lost, and that was the constraint the migration was
built around.**
- Sessions record the tmux socket they were born on, so the ones started
before the rename keep running on the old server and keep working. Moving a
session between tmux servers is not possible; not needing to is the design.
- Any stray pre-rename session that is *not* in the panel's own state can be
taken over with **Adopt** — the old socket is now scanned as a foreign one,
which is exactly what it has become.
- The browser's own state — sidebar width, collapsed state, open tabs — is
carried across once on first load. Reopening to a default sidebar and no
tabs is the moment a rename feels like a breakage.
- **Served at `/clique` behind the tunnel.** The old `/mux` path still
resolved, so an existing bookmark would not 404.
- The password moved with everything else, to `$CLIQUE_HOME/password`.
## 0.4.1 — 2026-08-18 20:24 PDT
The palette's "most recently used" order now lives on the server with the rest
of the settings, so it is the same order on the desktop, on another machine,
and on a phone. It was per-browser for one release, which meant a new device
started with the ordering of a stranger.
The rule this follows, and it is worth writing down: **anything about the work
syncs, anything about the screen stays local.** Which session you were last in
is about the work. Sidebar width is about the screen — a 420px sidebar that
suits a desktop is wrong on a laptop and absurd on a phone — so that one stays
in the browser where it belongs.
## 0.4.0 — 2026-08-18 20:22 PDT
**A command palette.** `Ctrl`+`K` opens one box that reaches every action and
every session. It is the feature three independent LLM feature lists ranked
first, and the reason they gave is the right one: numbered tabs work at five
sessions and become friction at thirty, and without a palette every feature
added from here arrives as one more button in the chrome.
- Type nothing and it is a session switcher, **most-recently-used first**, so
`Ctrl`+`K` `↵` is "back to the one I was just in".
- `>` narrows to commands, `@` to sessions — VS Code's prefixes, because
muscle memory is the point.
- Fuzzy matching that scores *where* a match lands: the start of a word beats
the middle of one, and adjacent letters beat scattered ones. Only the title
is highlighted; marking letters inside a directory path reads as damage.
- Commands cover what was previously button- or right-click-only: new session,
new folder, adopt, settings, sidebar, history, rename, archive, copy working
directory, close tab, close every tab, kill — plus every theme, every
appearance and every snippet, live.
- `Ctrl`+`Shift`+`P` opens it straight into commands.
- Escaping out gives focus back to wherever it came from, including the pane
you were typing in.
**The pane keeps the keyboard, except for this.** `Ctrl`+`K` is readline's
kill-to-end-of-line, so it is taken from the terminal explicitly rather than
by accident — and it can be handed back in Settings → Appearance → Keyboard,
where `Ctrl`+`Shift`+`P` still opens the palette either way.
Also in this release:
- New panes are built with the active theme already applied. Under a light
theme, opening a session used to flash a black pane for a moment.
- The context menu's session actions became named functions, so the palette
runs the same code rather than a second copy of it.
## 0.3.1 — 2026-08-18 20:07 PDT
Real logos, drawn properly. Icons are now classified automatically into two
kinds, because they cannot be drawn the same way:
- A **glyph** — one colour on transparency — stays a CSS mask, so the panel
supplies the colour and one file serves the tinted, grey and status-coloured
modes.
- A **badge** — a logo with its own background or several colours — is drawn
as a real image. Used as a mask it flattened to a solid square, which is
exactly how Cline and OpenCode were rendering.
Gemini and Cursor gained their true multi-colour marks, and the tintable ones
now carry their real brand colours rather than approximations.
Detection is by inspecting the file, not by a flag in config, so dropping a
new icon into the directory does the right thing without anyone classifying it
correctly.
## 0.3.0 — 2026-08-18 18:52 PDT
Security pass, driven by reading Codeman's hardening documentation and
comparing it line by line. Four real gaps, one of them serious. Full model now
written down in [SECURITY.md](SECURITY.md).
- **Cross-Site WebSocket Hijacking is closed.** A WebSocket handshake is not
subject to CORS and `SameSite=Lax` does not cover it, so any page you visited
could have opened a socket to this origin and had the browser attach your
session cookie — a live root terminal for a hostile page. The upgrade now
validates `Origin` before completing.
- **DNS rebinding is closed.** A `Host` allowlist runs before auth and before
any handler. Loopback, IP literals, `.ts.net`, common tunnel providers, plus
anything in `CLIQUE_ALLOWED_HOSTS`.
- **Login throttling no longer locks out the legitimate user.** Behind a tunnel
every request arrives from the same loopback address, so a per-IP lockout hit
the only real user along with the attacker. A correct password now always
gets through.
- **Content-Security-Policy** added. Everything is served from this origin, so
a strict policy was free.
- **The password is stored as an scrypt hash.** The server only verifies, so
keeping the plaintext bought nothing. Set it with
`python3 -m clique password`. Keep the plaintext in a password manager,
not next to the hash.
- **API tokens hot-reload.** Minting an agent no longer needs a restart, and
more importantly, revoking one takes effect immediately — a revocation that
waits for a restart is not a revocation.
## 0.2.3 — 2026-08-18 18:45 PDT
The CLI icon can now carry the status colour, so a session shows one mark
instead of two: shape says which CLI, colour says how it is doing. Off by
default; the toggle is in Settings → CLI markers. The separate dot returns
automatically wherever there is no icon to carry it.
Fixed while testing it: "someone is watching this" was never true. Each browser
attaches to a grouped viewer session rather than the session itself, so the
session's own client count was always zero and every session sat permanently on
the unwatched colour. Viewer attachment is now folded back into the session it
mirrors, so green and amber mean something again — a regression that had been
invisible while a dot nobody looked at was carrying it.
## 0.2.2 — 2026-08-18 18:42 PDT
Draggable sidebar. Grab the edge, or focus it and use the arrow keys;
double-click resets. The width is remembered per browser and comes back when
the sidebar is collapsed and reopened.
Stored in localStorage rather than server settings, unlike everything else in
the settings sheet: a 420px sidebar that suits a desktop is wrong on a laptop
and absurd on a phone. This is the one preference that is about the screen
rather than about the person.
Real vendor marks for eight more CLIs — OpenCode, Goose, Factory Droid, Cline,
plus Codex, Copilot, Cursor and Qwen from simple-icons. Twelve of sixteen now
have a real icon. Aider publishes a wordmark rather than a mark and three
others have no icon at a stable URL, so those four keep the letter badge,
which is documented as a choice rather than left looking like a gap.
## 0.2.1 — 2026-08-18 18:36 PDT
The settings dialog is now furniture: fixed 560×640, same position, every tab.
It used to size itself to whichever tab was open, so switching resized and
re-centred it — the content jumped under the cursor and the tab strip moved out
from under the pointer that had just clicked it. Now the sheet is a fixed-height
column, the header and tab strip are fixed rows, and only the pane area
scrolls. The scrollbar gutter is reserved whether or not it is showing, so a
short tab and a long one lay out identically instead of shifting by its width.
The per-CLI rows became a grid rather than a flex row. The select was squeezing
the label column, so names wrapped onto two lines and every row was a different
height. Sixteen rows, all 32px, selects aligned to the pixel.
## 0.1.0 — 2026-08-18 17:18 PDT
First working version, replacing Codeman for day-to-day use.
- tmux session engine on its own server, with a CLI type registry so adding a
coding CLI is a config block rather than a code change.
- Stdlib HTTP server, JSON API and a hand-rolled WebSocket for live terminals.
A PTY is created only while a browser is watching it.
- Sidebar (folders, drag-drop, rename, search, rail), numbered tabs, xterm.js
terminal, input bar with mode pill and Run/Shell split, stats readout.
- Password gate, mandatory: this serves a terminal running as root.
- Published on the tailnet at /mux, under systemd.
Fixed before release:
- Each browser now attaches through its own grouped tmux session. Sharing one
meant tmux sized the pane to the smallest client — which would have resized
an adopted session under the tool still running it.
- Scrollback is captured history-only; tmux redraws the visible frame on attach
and sending it too printed the last screenful twice.
- `[hidden]` did nothing against an id selector that set `display`, so the
new-session modal covered the whole app from page load.