diff --git a/CHANGELOG.md b/CHANGELOG.md index 25c272ad..04c2e9a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## 7.8.0 +The editor you actually use is one click away, the composer stops moving under the +cursor, and a terminal pane you can no longer type into has two ways back. + ### Open the workspace in VS Code - **One button, local or remote.** A project row and the file viewer now hand the @@ -31,6 +34,53 @@ opens a remote *file* link as a folder, so the button comes off rather than doing the wrong thing — the project row still opens the remote workspace. +### The composer stopped moving under the cursor + +Four defects reported against the shipped build, none of them a regression: each was +work that never reached a commit. Two of them are the composer, two the terminal pane. + +- **The interrupt pill no longer shoves the placeholder.** It sat upstream of the + textarea in a flex row, so every earlier sibling owned the text's left edge: the + moment a turn started, the hint jumped ~70px to the right and the narrower box + re-wrapped it. The pill now lives downstream of the textarea, where it appears and + disappears without moving a glyph. + +- **One place computes the composer's height.** The formula was already right; it + simply ran from one function. Boot (the markup ships `rows="1"`), a language switch + (a longer translation wraps differently), a sidebar toggle, a window resize and the + pill entering the row all changed the wrap without re-measuring, which is what left + the second line of the hint clipped. `autosizeInput()` now owns the cap and is + called from all seven paths. + +An adversarial review of the diff (an independent non-Claude reviewer, run before the +merge) turned up four more, all fixed here: the composer was measured at the *start* of +the 250ms sidebar animation, a superseded socket's `close` event could mark a working +terminal "disconnected", a bare Return — the one key you most need at a `[Y/n]` prompt — +was the one key the send line refused to send, and a draft left in that line followed the +user to whichever terminal they opened next. + +### Terminal sessions + +- **A pane no longer goes deaf until you switch tabs and back.** xterm routes + keystrokes through a hidden textarea; a reconnect swapped the socket but left focus + wherever it had drifted, so the pane looked live and swallowed every key. The tab + switch "fixed" it only because `showTerminalView()` ends on `term.focus()`. A + reconnect and a click on the pane now do the same -- and a click is ignored while a + selection is being dragged out. + +- **A reconnect button in the terminal header.** Every automatic path is conditional: + the visibility handler and the tab switch only act on a socket that already reports + closing, and `onclose` only fires if a close event ever arrives. A socket whose TCP + path died without a FIN reports OPEN on both ends -- the server heartbeat kills those + within ~60s, but that is a timeout, not an answer to "it is stuck now". + +- **A send line under the pane.** The composer is `display: none` in terminal mode, so + the only way to send a line to a terminal session was to type into the pane -- exactly + what a stale socket takes away. The new input writes the same `{type:'input'}` frame + the pane writes, so it works on an agent tab and on the subscription engine's pane, + where it is how a blocking permission prompt gets answered. It refuses to send on a + socket that is not OPEN rather than dropping the line silently. + ## 7.7.0 Three things that were true only in one place become true everywhere: the file diff --git a/CLAUDE.md b/CLAUDE.md index afe4dbb4..827ef7fc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,7 +20,7 @@ docker compose up -d docker compose logs -f claude-chat ``` -No linting and no build step configured. `npm test` chains 65 test files under `test/`: 18 DOM-less render/UI-logic tests (`test/render/*.test.mjs`, run through `node --test`) plus 47 plain-`node` suites in `test/` covering the overload detector, env load order, multi-agent results, terminals, bots, telegram, updates, kanban scheduling, i18n completeness, the config precedence resolver plus its secret masking, usage-limit detection, the filesystem path guard (including the SVG sandbox header and the symlink rule on the `@`-mention search endpoints) plus the tunnel-blocks-terminal rule, WS session re-subscription, the SSH remote CLI-session import, the live engine pane / interactive-prompt watchdog, the cross-project global workspace aggregation, the rule that an SSH credential never leaves the server process, the Windows command-quoting oracle, the auth token lifecycle, the multi-agent dependency scheduler (waves, plan sanitising, and the rule that a failure warning must survive dep-context truncation), the SSH stream parser's three guards, the remote non-interactive shell environment (`remote-env.test.js`, which runs the generated prelude through real `bash -lc`: it must parse, print nothing on stdout, and never end on a false test — the caller chains `&& claude …` behind it), the remote CLI-list framing parser, the bot inbox's SQL seam (`bot-inbox.test.js` pins that `from_bot AS "from"` keeps the exact key `planInboxDelivery` reads — rename one without the other and every letter is silently retired as malformed), the one-time config/.env migration onto CCS_CONFIG_PATH and the mid-task clarification delivery contract on the subscription engine (`interrupt-delivery.test.js` — pins that the tmux injection block sits BEFORE the poll loop's completion `break`, that draining does not imply delivery, that a failed paste is re-queued and warns non-terminally, and that the task runner passes the same callbacks the chat path does), the CLAUDE.md / AGENTS.md discovery rules (`agents-md.test.js`, which also pins that AGENTS.md reaches the subprocess as `--append-system-prompt` and never as `--system-prompt`), and the remote file browser's three guard layers (`remote-files.test.js` runs the generated POSIX script through a real `/bin/sh` against a temp tree that contains symlinks OUT of the project; `remote-files-api.test.js` boots a server against a fake remote via `CCS_REMOTE_EXEC_HOOK` and drives `/api/files` the way the SPA does), the editor deep links (`editor-links.test.js` pins the two URI shapes literally — the browser link puts `vscode-remote` in the AUTHORITY and the CLI argument puts it in the SCHEME, and collapsing the two silently breaks one path; `editor-open-api.test.js` boots a real server with `PATH` pointed at an EMPTY directory, which both makes the `opened:'client'` fallback deterministic and guarantees the suite never launches an editor window on a developer's desktop), and the new-chat defaults chain (`chat-defaults.test.js` pins the pure resolver — the built-ins are asserted to be exactly what the SPA hardcoded before #58, and the choice lists to be exactly the toolbar's `data-v` sets and `MODEL_MAP`'s aliases; `chat-defaults-api.test.js` boots a real server in a throwaway `APP_DIR` and pins that a project writes back a SPARSE override object — a five-key snapshot passes every other assertion in that file and still breaks the feature). On the render side, `tables.test.mjs` also pins the ReDoS bound in renderMd step 3.4, `xss.test.mjs` runs 24 adversarial payloads end-to-end, and `forged-tokens.test.mjs` covers the case where user text contains the renderer's own placeholder control bytes, and `pane-font.test.mjs` pins the clamp DIRECTION of `_fitEnginePaneFont` (a wide engine pane may only shrink; a narrow split pane must be allowed to grow). `script-scope.test.mjs` pins which `