Skip to content

Batch of small UX fixes: permission stream, compact mode, settings, CLI session list, keybindings, export, copy - #220

Open
leonidasbarkas98-cpu wants to merge 12 commits into
andrepimenta:mainfrom
leonidasbarkas98-cpu:feat/ux-quality-of-life-batch
Open

leonidasbarkas98-cpu wants to merge 12 commits into
andrepimenta:mainfrom
leonidasbarkas98-cpu:feat/ux-quality-of-life-batch

Conversation

@leonidasbarkas98-cpu

Copy link
Copy Markdown

A batch of unrelated small UX fixes and additions, grouped into one PR because each is small on its own. In commit order:

  • Permission requests dying with "Stream closed": result closed the Claude process stdin immediately, tearing down the stdio control channel while a can_use_tool request could still be in flight. stdin.end is now deferred until result was seen and no permission request is pending, retried after each answered prompt; a re-entrancy guard also rejects a second send while a process is still running.
  • A compact chat UI mode (claudeCodeChat.ui.compactMode): reduces padding/margins/font size/line height of messages, tool blocks and code blocks for a denser layout. Density only, nothing hidden or restructured; applies live via the existing settings transport.
  • The three settings above (plus the pre-existing completion popup/sound options) surfaced in the in-app settings modal (gear icon), not just native VS Code settings.
  • A Modes popup replacing the separate Plan/Ultrathink input toggles: four permission modes (Manual, Edit automatically, Plan, Auto) plus an effort slider (Low/Medium/High/Extra high/Max), matching the standard Claude Code CLI UI. Selection is persisted per workspace, applied via --permission-mode/--effort on every spawn (Manual sends no flag, byte-identical to the previous default spawn), and survives queued messages, slash commands, plan-file sends and panel reloads.
  • A Compact button with a summarize-and-restart flow: since the headless CLI does not interpret /compact as a command, compaction is emulated by sending a summarize turn to the running session, capturing the result as a seed, and arming a forced fresh (non---resume) session for the next message, prefixed by the summary. A failed summarize still arms the fresh session (without a seed) so an overflowed conversation isn't stuck re-resuming into the same overflow.
  • A "CLI Sessions" section in the conversation history panel, listing the 25 most recent Claude Code CLI sessions for the current workspace read from ~/.claude/projects/<slug>/*.jsonl, deduplicated against sessions the extension already knows. Clicking one adopts the session id and renders a bounded preview; the next message continues it through the normal --resume path.
  • A fix keeping the CLI session id tied to the conversation on screen: loading a conversation from history now adopts its stored session id (or starts fresh if the CLI transcript no longer exists) instead of resuming whichever session was previously active, and stale stdout from a superseded process is now ignored instead of being able to reinstate a killed session.
  • Removed the default Ctrl+Shift+C keybinding for the new Compact button conflicting with the VS Code terminal's copy shortcut (scoped to !terminalFocus now); README documents how to rebind or disable it.
  • A download button next to delete on each history entry, exporting the stored conversation JSON as-is (filename validated the same way as the delete path).
  • The message copy button now copies the original markdown instead of innerText, so ordered lists keep their original numbers instead of being renumbered/flattened by the rendered DOM. Falls back to the old DOM read when no raw markdown was recorded for a message. Side effect: messages with code fences now copy as markdown source (including fence lines) instead of flattened rendered text.
  • Two cleanup commits: dropped two settings-modal checkboxes that referenced unregistered package.json config keys (belonging to a notification feature intentionally out of scope for this batch — config.update() on an unregistered key throws and was silently dropping every other settings change in the same save), and requalified internal-tracker issue references in code comments so they don't auto-link to unrelated issues on this repo.

This is one of several PRs from our fork submitted together as part of a broader, thematically grouped series (permission/session handling, settings/UI, security hardening, rendering, etc. split across separate branches/PRs). This particular branch is based directly on current main (ab6e307) and does not stack on any other PR in the series.

tsc --noEmit clean, no new dependencies.

🤖 Generated with Claude Code

Jonas Kunert and others added 12 commits July 28, 2026 13:03
Root cause (regression from a156881, MCP-file -> stdio migration):
the 'result' event closed the Claude process stdin immediately, tearing
down the stdio control channel while can_use_tool requests could still
be in flight -> every permission prompt aborted instantly.

- Defer stdin.end via _maybeEndClaudeStdin: close only once 'result'
  was seen AND no permission request is pending (500ms grace window),
  retried after each answered prompt so the process still exits cleanly.
- Re-entrancy guard in _sendMessageToClaude: reject a second send while
  a process is running (overlapping --resume spawns fought over the
  session lock; queueing stays fork-issue-16).
- [perm] diagnostic logging on spawn/result/can_use_tool/response/kill
  to prove the failing path at runtime if it ever reoccurs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New setting claudeCodeChat.ui.compactMode (default off): reduces
paddings, margins, font sizes and line heights of chat messages,
tool blocks and code blocks for a denser layout. Nothing is hidden
or restructured - density only; the normal layout is untouched.

- Reuses the existing settingsData transport (_sendCurrentSettings)
  to push the flag into the webview, which toggles a body-level
  compact-mode class; all overrides live in one scoped CSS block.
- onDidChangeConfiguration listener extended so changing the setting
  in the native VS Code settings UI applies live without a reload.

Also ships temporary file-based [perm] diagnostics for fork-issue-15: console.error
of an installed extension is not persisted anywhere readable, so _permLog()
mirrors all permission-channel events (stdin close/error, stdin.end stack,
can_use_tool stdin state, process close) to os.tmpdir()/claude-code-chat-perm.log.
To be removed once fork-issue-15 is root-caused.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds three checkboxes to the webview settings modal (gear icon) so the
recently introduced options are discoverable in-app, not only in the
native VS Code settings UI:
- notifications.completionPopup (default on)
- notifications.completionSound (default off)
- ui.compactMode (default off)

New 'Notifications' and 'Appearance' sections follow the existing
settings-group markup; values ride the existing settingsData /
updateSettings transport (_sendCurrentSettings now also carries the two
notification keys; _updateSettings is generic already). compactMode keeps
its single effect path (config change -> settingsData -> body class).

Also updates the _maybeEndClaudeStdin doc comment with the fork-issue-15 perm-log
findings; behavior unchanged — the reviewed close logic stays until the
lifecycle rework lands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ue-31)

Replace the Plan and Ultrathink input toggles with a single Modes popup
matching the standard Claude Code UI: four permission modes (Manual,
Edit automatically, Plan, Auto) with descriptions and a checkmark on
the active one, plus an effort slider (Low/Medium/High/Extra high/Max)
and a shift+tab cycle hint.

- mode and effort are provider state persisted in workspaceState
  ('claude.selectedMode'/'claude.selectedEffort', same pattern as the
  model selection), pushed to the webview on init (modeSelected/
  effortSelected) and applied on every process spawn: --permission-mode
  for acceptEdits/plan/auto (Manual sends no flag, byte-identical to
  the previous default spawn) and --effort once explicitly chosen
  (no flag before first interaction, CLI default stays in charge),
- selection therefore now also applies to queued messages, slash
  commands and plan-file sends, and survives panel reloads,
- the ULTRATHINK prompt-prefix block is removed; --effort is the
  native replacement (the thinking.intensity setting and its modal
  stay dormant, cleanup is a follow-up),
- _sendMessageToClaude loses the per-message planMode/thinkingMode
  parameters; the message queue carries only id/message/images,
- popup follows the existing connect-menu pattern; effort slider
  labels get scoped flex rules so five labels fit the popup width,
- shift+tab in the input cycles through the four modes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The headless CLI does not interpret /compact as a command (verified on
the target system: the text is passed verbatim to the model), so a real
compaction is emulated: the toolbar button sends a summarize turn to
the running session (full context, no user echo, no backup commit),
captures the result as a seed, and arms a forced fresh session — the
next user message starts without --resume, prefixed by the summary.

A failed summarize (e.g. an API 400 after context overflow) still arms
the fresh session, just without a seed, giving a one-click way out of
an otherwise dead conversation that would re-resume into the same
overflow forever. A saved separator message marks the boundary in the
chat and on reload; the conversation filename is pinned so the fresh
session keeps writing the same history file. The slash modal's
/compact entry now uses the same flow instead of the dead verbatim
send, and the context indicator highlights the button from 85% usage.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ssue-37)

The history panel now shows a separate "CLI Sessions" section with the
25 most recent Claude Code CLI sessions of the current workspace, read
from ~/.claude/projects/<slug>/*.jsonl (slug matched case-insensitively,
CLAUDE_CONFIG_DIR respected). Titles come from a bounded 64 KB head
read (first real user line), entries already known to the extension's
own history are deduplicated away, and the JSONL files are never
written to.

Clicking an entry resets the conversation state, adopts the CLI
session id and renders a display-only preview of the last ~20 messages
from a bounded 512 KB tail read; the next message then continues that
session through the regular --resume path and is saved as a normal
extension conversation. The line parser is strictly defensive since
the JSONL format is CLI-internal and may change between versions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sue-41)

Three confirmed paths let an old session bleed into the visible chat:
loading a conversation from history never adopted its sessionId, so the
next turn resumed whichever session was active before and the following
save poisoned the file on disk with the foreign id - the load path now
adopts the stored id when its CLI transcript still exists (fresh start
otherwise) and drops pin/checkpoints only when switching files. Stdout
of a superseded process had no stale guard (close did), so a late
result line could reinstate a killed session - the handler now ignores
non-current processes. The compact pin (fork-issue-36) lived only in RAM and a
reload split the conversation in two files - it now rides along in the
persisted panel state. The spawn log records the effective
resume/fresh decision for future field diagnosis.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…inding (fork-issue-45)

Ctrl+Shift+C collided with terminal copy; the binding now carries
when !terminalFocus and the README explains how to rebind or disable it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit a4e41f5ad9a45412c20198fd1c304d601a37fbf0)
Adds a download button next to the delete button on each history entry.
The host validates the filename exactly like the delete path, reads the
stored JSON untouched and writes it wherever the save dialog points
(Downloads, then workspace, then home as the default location).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit d970e21dbcf9f866222a97a8c7b6c4a341d2c1d0)
…sue-46)

The message copy button read innerText, so ordered lists lost or
renumbered their markers (parseSimpleMarkdown strips the literal digits
and interrupted lists restart at 1). addMessage now records the raw
markdown in a WeakMap and the copy handler prefers it, falling back to
the old DOM read when no raw text exists. Side effect worth noting:
messages with code fences now copy as markdown source including the
fence lines, instead of flattened rendered text.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 1b406605a13f3162c623aeb0b4bfa387800321f5)
completionPopup/completionSound were read/written by fork-issue-29 but their
package.json configuration entries come from the fork-issue-17 notification
feature, which is intentionally not part of this batch. Since VS Code
rejects config.update() for unregistered keys, every settings save
would throw mid-loop and silently drop all other settings changes.
Remove the two orphaned checkboxes and their read/write wiring instead
of registering the keys (that would pull fork-issue-17 scope in unannounced).
Bare #NN comment references pointed at our private tracker, not this
repo's issues; requalify as fork-issue-NN so GitHub doesn't auto-link
them to an unrelated issue here. Genuine "upstream andrepimenta#98" references are
left untouched.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant