From 6529adc2acb0a66f6a1fd2f893ae81a5ef8910aa Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 6 Sep 2026 09:12:56 +0530 Subject: [PATCH 1/6] feat(ai): styles and string for the collated tool card pile Pile header, count pill, stacked card edges and the ghost fold animation overlay for the AI panel tool card groups. Also renders the five newest messages without the content-visibility intrinsic-size placeholder, which made every appended message occupy 300px for one frame and jolt a bottom-pinned list. --- src/nls/root/strings.js | 1 + src/styles/Extn-AIChatPanel.less | 186 +++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index a45d9cbae1..74c0a360fe 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -2681,6 +2681,7 @@ define({ "AI_CHAT_TOOL_EDIT_FILE": "Edit {0}", "AI_CHAT_TOOL_WRITE_FILE": "Write {0}", "AI_CHAT_TOOL_RAN_CMD": "Ran command", + "AI_CHAT_TOOL_GROUP_STEPS": "{0} steps", "AI_CHAT_TOOL_SKILL_NAME": "Skill: {0}", "AI_CHAT_TOOL_IN_PATH": "in {0}", "AI_CHAT_TOOL_INCLUDE": "include {0}", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 93ec1a51fc..1460843f7a 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -883,6 +883,15 @@ content-visibility: auto; contain-intrinsic-size: auto 300px; + // A message appended at the bottom is laid out as the 300px placeholder + // for one frame before it renders, which makes a bottom-pinned list + // jolt on every new card or bubble. The newest messages are on screen + // anyway, so render them normally; once older, they carry a remembered + // size and switch to `auto` without a size change. + &:nth-last-child(-n + 5) { + content-visibility: visible; + } + .ai-msg-label { font-size: @ai-text-meta; color: @project-panel-text-2; @@ -1421,6 +1430,183 @@ } } +/* ── Collapsed run of finished tool cards ──────────────────────────── */ +// A long run of finished tool cards (e.g. 20 "Ran command" rows) folds into +// one of these; see core-ai/AIToolGroup.js. Collapsed, it is drawn as a +// stack of cards: the header card on top with two offset card edges +// peeking out below it, so the pile reads as "several cards" at a glance. +// The header shows the distinct tool icons and the step count, and expands +// to the original cards on click. +@ai-tool-group-stack-step: 3px; + +.ai-tool-group { + position: relative; + margin-bottom: 6px; + // room for the two stacked card edges below the header + padding-bottom: (@ai-tool-group-stack-step * 2); + + .ai-tool-group-header { + position: relative; + z-index: 2; + display: flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + cursor: pointer; + border-radius: 4px; + background-color: @bc-ai-sidebar-bg; + background-image: linear-gradient(rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.035)); + border: 1px solid rgba(255, 255, 255, 0.06); + border-left: 2px solid var(--tool-color, @project-panel-text-2); + min-width: 0; + + &:hover .ai-tool-group-label { + opacity: 1; + } + } + + // The two card edges "behind" the header. Each is narrower and lower + // than the one above it, like a fanned pile of cards. + &::before, + &::after { + content: ""; + position: absolute; + left: 0; + right: 0; + height: 10px; + border-radius: 0 0 4px 4px; + background-color: @bc-ai-sidebar-bg; + background-image: linear-gradient(rgba(255, 255, 255, 0.02), rgba(255, 255, 255, 0.02)); + border: 1px solid rgba(255, 255, 255, 0.05); + border-top: none; + pointer-events: none; + } + + &::before { + z-index: 1; + left: 4px; + right: 4px; + bottom: @ai-tool-group-stack-step; + } + + &::after { + z-index: 0; + left: 8px; + right: 8px; + bottom: 0; + } + + .ai-tool-group-chevron { + width: 14px; + text-align: center; + flex-shrink: 0; + font-size: @ai-text-meta; + color: @project-panel-text-2; + opacity: 0.6; + transition: transform 0.15s ease; + } + + .ai-tool-group-icons { + display: flex; + align-items: center; + gap: 4px; + flex-shrink: 0; + } + + .ai-tool-group-icon { + font-size: @ai-text-secondary; + width: 14px; + text-align: center; + } + + .ai-tool-group-label { + font-size: @ai-text-body; + color: @project-panel-text-2; + opacity: 0.75; + line-height: @ai-line-compact; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; + } + + // Count pill at the right edge of the header: the number of collated + // cards is the one fact the collapsed pile must convey. + .ai-tool-group-count { + margin-left: auto; + flex-shrink: 0; + font-size: @ai-text-meta; + line-height: 1; + padding: 3px 7px; + border-radius: 9px; + color: @project-panel-text-1; + background-color: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(255, 255, 255, 0.06); + } + + .ai-tool-group-body { + display: none; + margin-top: 4px; + padding-left: 8px; + border-left: 1px solid rgba(255, 255, 255, 0.06); + } + + // Count pill bump when a card lands in the pile. + .ai-tool-group-count.ai-tool-group-count-bump { + animation: ai-tool-group-count-bump 0.3s ease-out; + } + + &.ai-tool-group-expanded { + padding-bottom: 0; + + &::before, + &::after { + display: none; + } + + .ai-tool-group-chevron { + transform: rotate(90deg); + } + + .ai-tool-group-body { + display: block; + } + } +} + +@keyframes ai-tool-group-count-bump { + 0% { transform: scale(1); } + 40% { transform: scale(1.35); } + 100% { transform: scale(1); } +} + +// A finished card being collated into the pile above it. The real card is +// moved into the (hidden) group body at once; this floating copy is laid +// over its old spot and flies up into the pile header, shrinking and fading. +// The copies live in a fixed overlay over the messages container, outside +// the scroll container, so they never touch its scroll height. Duration +// must match FOLD_ANIMATION_MS in core-ai/AIToolGroup.js. +.ai-tool-group-ghost-layer { + position: fixed; + overflow: hidden; + pointer-events: none; + z-index: 1000; +} + +.ai-msg-tool.ai-tool-group-ghost { + position: absolute; + margin: 0; + overflow: hidden; + transform-origin: top center; + will-change: transform, opacity; + transition: transform 0.28s ease-in, opacity 0.28s ease-in; + + &.ai-tool-group-ghost-active { + transform: translateY(var(--ghost-dy, -40px)) scale(0.94, 0.15); + opacity: 0; + } +} + /* ── TodoWrite task list widget ────────────────────────────────────── */ .ai-todo-list { padding: 2px 8px 4px 0; From f12b3c9dbfc0a9f08604a2c41c20d19a36c504a8 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 6 Sep 2026 09:48:32 +0530 Subject: [PATCH 2/6] feat(ai): styles and strings for tool card failure tag, copy, truncation Capped, scrollable tool detail with the thumb shown while hovering the AI panel; failed card accent and tag; pile failure count; Copy button on expanded cards; truncation note for restored detail. --- src/nls/root/strings.js | 3 + src/styles/Extn-AIChatPanel.less | 97 ++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 74c0a360fe..d65d27064d 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -2682,6 +2682,9 @@ define({ "AI_CHAT_TOOL_WRITE_FILE": "Write {0}", "AI_CHAT_TOOL_RAN_CMD": "Ran command", "AI_CHAT_TOOL_GROUP_STEPS": "{0} steps", + "AI_CHAT_TOOL_GROUP_FAILED": "{0} failed", + "AI_CHAT_TOOL_FAILED_TAG": "failed", + "AI_CHAT_TOOL_DETAIL_TRUNCATED": "Truncated: only the first {0} characters were kept in the chat history", "AI_CHAT_TOOL_SKILL_NAME": "Skill: {0}", "AI_CHAT_TOOL_IN_PATH": "in {0}", "AI_CHAT_TOOL_INCLUDE": "include {0}", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 1460843f7a..17e87d4a6e 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -1210,6 +1210,28 @@ .ai-tool-detail { display: none; padding: 2px 10px 6px 28px; + // A long command or script must not turn the card into a wall of + // text: cap the detail at roughly nine lines and scroll inside it. + max-height: 160px; + overflow-y: auto; + overflow-x: hidden; + + // The global scrollbar theme paints the thumb only while hovering + // the scrolling element itself, which hides the fact that there is + // more text below the cap. Show it while the pointer is anywhere + // over the AI panel instead. + &::-webkit-scrollbar { + background-color: transparent; + } + .ai-chat-panel:hover &::-webkit-scrollbar-thumb { + border-radius: 999px; + background-color: rgba(255, 255, 255, 0.28); + background-clip: padding-box; + border: 2px solid transparent; + } + .ai-chat-panel:hover &::-webkit-scrollbar-thumb:hover { + background-color: rgba(255, 255, 255, 0.45); + } } .ai-tool-screenshot { @@ -1233,6 +1255,27 @@ word-break: break-all; } + // Restored cards keep only the beginning of a long detail; this note + // sits under the kept text so the cut is unmistakable. + .ai-tool-detail-truncated { + display: flex; + align-items: center; + gap: 6px; + margin-top: 4px; + padding: 3px 8px; + border-radius: 4px; + font-size: @ai-text-meta; + font-style: italic; + color: #e8a838; + background-color: rgba(232, 168, 56, 0.08); + border: 1px dashed rgba(232, 168, 56, 0.45); + line-height: @ai-line-compact; + + i { + flex-shrink: 0; + } + } + .ai-tool-preview { font-size: @ai-text-secondary; font-family: 'SourceCodePro-Medium', 'SourceCodePro', monospace; @@ -1269,6 +1312,21 @@ opacity: 1; } + // Copy button at the right edge of the header row, identical to the + // markdown code block one (icon + "Copy", "Copied!" with a tick); copies + // the card's detail lines. Only present while the card is expanded: + // there is nothing to copy from a card whose detail is hidden. + .ai-tool-header .ai-tool-copy-btn { + display: none; + margin-left: auto; + flex-shrink: 0; + transition: background-color 0.15s ease, color 0.15s ease; + } + + &.ai-tool-expanded .ai-tool-header .ai-tool-copy-btn { + display: inline-block; + } + // Inline edit actions (diff toggle) — sits at the right edge of the // tool header so it shares a row with the "Edit " label. .ai-tool-header .ai-tool-edit-actions { @@ -1430,6 +1488,30 @@ } } +/* ── Failed tool call ──────────────────────────────────────────────── */ +// A card whose call returned an error: red accent and a "failed" note at +// the right of its header. Set by AIToolGroup.markCardFailed, live and on +// restore; the pile header counts these. +.ai-msg-tool.ai-tool-failed { + border-left-color: #e57373; + + .ai-tool-copy-btn + .ai-tool-failed-tag { + margin-left: 6px; + } + + .ai-tool-failed-tag { + margin-left: auto; + flex-shrink: 0; + font-size: @ai-text-meta; + line-height: 1; + padding: 2px 6px; + border-radius: 8px; + color: #e57373; + border: 1px solid rgba(229, 115, 115, 0.45); + background-color: rgba(229, 115, 115, 0.08); + } +} + /* ── Collapsed run of finished tool cards ──────────────────────────── */ // A long run of finished tool cards (e.g. 20 "Ran command" rows) folds into // one of these; see core-ai/AIToolGroup.js. Collapsed, it is drawn as a @@ -1530,6 +1612,21 @@ min-width: 0; } + // "N failed" note before the count pill, only when a collated tool call + // returned an error. + .ai-tool-group-failed { + margin-left: auto; + flex-shrink: 0; + font-size: @ai-text-meta; + color: #e57373; + opacity: 0.9; + white-space: nowrap; + + & + .ai-tool-group-count { + margin-left: 0; + } + } + // Count pill at the right edge of the header: the number of collated // cards is the one fact the collapsed pile must convey. .ai-tool-group-count { From a1a3ce79c94d7f29b241d8841e1cbcd0f40f7087 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 6 Sep 2026 10:10:40 +0530 Subject: [PATCH 3/6] fix(ai): cap only text tool detail blocks, not task or step lists --- src/styles/Extn-AIChatPanel.less | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 17e87d4a6e..65f9b40898 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -1210,6 +1210,11 @@ .ai-tool-detail { display: none; padding: 2px 10px 6px 28px; + } + + // Text detail (command, path, code lines) only — task lists and subagent + // step lists share .ai-tool-detail but always show in full. + .ai-tool-detail.ai-tool-detail-lines { // A long command or script must not turn the card into a wall of // text: cap the detail at roughly nine lines and scroll inside it. max-height: 160px; From aac975bd10a25013cdc3baa52d5148a587692bdc Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 6 Sep 2026 10:24:21 +0530 Subject: [PATCH 4/6] feat(ai): scroll-to-bottom button styles, pile label without count pill --- src/nls/root/strings.js | 1 + src/styles/Extn-AIChatPanel.less | 58 +++++++++++++++++++------------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index d65d27064d..20890cd9d8 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -2602,6 +2602,7 @@ define({ "AI_CHAT_NEW_SESSION_TITLE": "Start a new conversation", "AI_CHAT_NEW_BTN": "New", "AI_CHAT_THINKING": "Thinking...", + "AI_CHAT_SCROLL_TO_BOTTOM": "Scroll to bottom", "AI_CHAT_PLACEHOLDER": "Ask Claude...", "AI_CHAT_PLACEHOLDER_MODEL": "Ask {0}...", "AI_CHAT_SEND_TITLE": "Send message", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 65f9b40898..6854dbe660 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -62,6 +62,7 @@ } .ai-chat-panel { + position: relative; // anchors .ai-scroll-to-bottom-btn display: flex; flex-direction: column; -webkit-box-flex: 1; @@ -873,6 +874,34 @@ background-color: @bc-ai-sidebar-bg; } +// Floating "scroll to bottom" control over the lower right of the message +// list; shown by AIChatPanel._updateScrollToBottomBtn while the list is +// scrolled away from its end (top is set from the list's bottom edge). +.ai-scroll-to-bottom-btn { + position: absolute; + right: 18px; + z-index: 5; + width: 30px; + height: 30px; + border-radius: 50%; + border: 1px solid rgba(255, 255, 255, 0.12); + background-color: #3a3a3a; + color: @project-panel-text-1; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + font-size: @ai-text-secondary; + opacity: 0.9; + + &:hover { + opacity: 1; + background-color: #454545; + } +} + /* ── Individual messages ────────────────────────────────────────────── */ .ai-msg { margin-bottom: 16px; @@ -1626,24 +1655,6 @@ color: #e57373; opacity: 0.9; white-space: nowrap; - - & + .ai-tool-group-count { - margin-left: 0; - } - } - - // Count pill at the right edge of the header: the number of collated - // cards is the one fact the collapsed pile must convey. - .ai-tool-group-count { - margin-left: auto; - flex-shrink: 0; - font-size: @ai-text-meta; - line-height: 1; - padding: 3px 7px; - border-radius: 9px; - color: @project-panel-text-1; - background-color: rgba(255, 255, 255, 0.08); - border: 1px solid rgba(255, 255, 255, 0.06); } .ai-tool-group-body { @@ -1653,9 +1664,10 @@ border-left: 1px solid rgba(255, 255, 255, 0.06); } - // Count pill bump when a card lands in the pile. - .ai-tool-group-count.ai-tool-group-count-bump { - animation: ai-tool-group-count-bump 0.3s ease-out; + // Label bump when a card lands in the pile. + .ai-tool-group-label.ai-tool-group-label-bump { + animation: ai-tool-group-label-bump 0.3s ease-out; + transform-origin: left center; } &.ai-tool-group-expanded { @@ -1676,9 +1688,9 @@ } } -@keyframes ai-tool-group-count-bump { +@keyframes ai-tool-group-label-bump { 0% { transform: scale(1); } - 40% { transform: scale(1.35); } + 40% { transform: scale(1.12); } 100% { transform: scale(1); } } From b9b09c845425bdfeb14cee3a391ffa908dbf8ab8 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 6 Sep 2026 10:32:16 +0530 Subject: [PATCH 5/6] feat(ai): styles and strings for the pile's read-files line --- src/nls/root/strings.js | 2 ++ src/styles/Extn-AIChatPanel.less | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index 20890cd9d8..b2634fb508 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -2684,6 +2684,8 @@ define({ "AI_CHAT_TOOL_RAN_CMD": "Ran command", "AI_CHAT_TOOL_GROUP_STEPS": "{0} steps", "AI_CHAT_TOOL_GROUP_FAILED": "{0} failed", + "AI_CHAT_TOOL_GROUP_READ_FILES": "Read {0}", + "AI_CHAT_TOOL_GROUP_MORE": "+{0} more", "AI_CHAT_TOOL_FAILED_TAG": "failed", "AI_CHAT_TOOL_DETAIL_TRUNCATED": "Truncated: only the first {0} characters were kept in the chat history", "AI_CHAT_TOOL_SKILL_NAME": "Skill: {0}", diff --git a/src/styles/Extn-AIChatPanel.less b/src/styles/Extn-AIChatPanel.less index 6854dbe660..0fe0e53d9a 100644 --- a/src/styles/Extn-AIChatPanel.less +++ b/src/styles/Extn-AIChatPanel.less @@ -1565,6 +1565,7 @@ position: relative; z-index: 2; display: flex; + flex-wrap: wrap; // the read-files abstract wraps onto its own line align-items: center; gap: 6px; padding: 6px 10px; @@ -1664,6 +1665,38 @@ border-left: 1px solid rgba(255, 255, 255, 0.06); } + // Second header line: the Read icon then "a.html, b.css +3 more", most + // recent first. Hidden while the pile is expanded since the cards show. + .ai-tool-group-summary { + display: flex; + align-items: center; + gap: 6px; + flex-basis: 100%; + margin-top: -2px; + padding-left: 20px; // under the label, past the chevron column + font-size: @ai-text-meta; + color: @project-panel-text-2; + opacity: 0.7; + min-width: 0; + + .ai-tool-group-summary-icon { + width: 14px; + text-align: center; + flex-shrink: 0; + } + + .ai-tool-group-summary-text { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; + } + } + + &.ai-tool-group-expanded .ai-tool-group-summary { + display: none; + } + // Label bump when a card lands in the pile. .ai-tool-group-label.ai-tool-group-label-bump { animation: ai-tool-group-label-bump 0.3s ease-out; From c843aa5a4ae3c4b466b90d614aa4b7ab6f469233 Mon Sep 17 00:00:00 2001 From: abose Date: Sun, 6 Sep 2026 11:16:05 +0530 Subject: [PATCH 6/6] build: update pro deps --- tracking-repos.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracking-repos.json b/tracking-repos.json index 283e04c7bf..7c8ebca8d8 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "033ca7b4524c0e01a5d412893f5aee99a164a5e5" + "commitID": "5b16dbf907820c5625fe8b985884ba83eca0eef1" } }