Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions webviews/src/agent-session/acpmux/chips/chips.css
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,31 @@
-webkit-box-decoration-break: clone;
box-decoration-break: clone;
}
/* A reference stays whole on one line: its mark, owner/repo#N and a title cut short. */
/* A reference stays whole on one line: its mark, owner/repo#N and its title, which is cut off
only when the chip is as wide as the line (it moves to a line of its own first). The negative
margin keeps the padding out of the line's height, as an inline chip's is. */
.cv-chip.is-ref {
display: inline-flex;
align-items: baseline;
max-width: 100%;
margin-block: -1px;
box-sizing: border-box;
white-space: nowrap;
}
.cv-chip.is-ref .cv-chip__icon {
flex: none;
align-self: center;
}
.cv-chip.is-ref .cv-chip__label {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.cv-chip.is-ref .cv-chip__title {
display: inline-block;
max-width: 32ch;
flex-shrink: 1000;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: bottom;
}
.cv-chip.is-ref:hover,
.cv-chip.is-path:hover {
Expand Down
2 changes: 1 addition & 1 deletion webviews/src/agent-session/acpmux/handoff/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
.acpmux-handoff-review :focus-visible:not(:where(input:not([type="button"], [type="submit"], [type="reset"], [type="checkbox"], [type="radio"], [type="range"], [type="color"], [type="file"], [type="image"]), textarea, [contenteditable]:not([contenteditable="false"]), [role="textbox"], [role="searchbox"])):not([role="menu"], .ui-popup) { outline: 1px solid var(--agent-muted); outline-offset: 2px; }
.acpmux-handoff-review [role=alert] { margin: 0; color: var(--agent-text); }
.acpmux-handoff-header-tools { margin-left: auto; display: flex; align-items: center; gap: 8px; min-width: 0; }
.acpmux-session-coverage { max-width: 160px; overflow: hidden; text-overflow: ellipsis; font-size: 11px; color: var(--agent-muted); }
.acpmux-session-coverage { min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; font-size: 11px; color: var(--agent-muted); }
.acpmux-handoff-targets { position: relative; }
.acpmux-handoff-targets > summary { list-style: none; cursor: pointer; color: var(--agent-muted); padding: 4px 8px; }
.acpmux-handoff-targets > div {
Expand Down
2 changes: 1 addition & 1 deletion webviews/src/agent-session/acpmux/styles.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,22 @@ export default agentPaneEntry({
summary(2, { status: "completed", durationMs: 74_000, toolCount: 4 }),
]),
},
"long-title": {
note: "A long title uses the composer's width and is cut off only at the column's edge.",
snapshot: chat([
user("Open a PR for the rulebook change", 4),
activity(
[
create(
"rulebook: reviewed PRs merge right away; the label is the fallback when the owner cannot merge, and base reds never block a merge",
634,
),
],
3,
),
assistant("Opened #634.", 2),
summary(2, { status: "completed", durationMs: 21_000, toolCount: 1 }),
]),
},
},
});
9 changes: 5 additions & 4 deletions webviews/src/agent-session/acpmux/summary/PullRequestDock.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// The chat's pull requests above the composer (cx-ncc.81, Claude desktop's PR bars): each one a
// `gh pr create` in the chat opened (sessionSummary.ts), newest first, with its number, repository,
// title and state, each a chip as wide as its text (the title cut off at a fixed width) with its
// dismiss button inside. The chat folder's pull request, which the header chip follows live, shows its
// live state and title. The newest two show and the rest wait behind Show more; a dismissed bar
// stays hidden for this chat (the page's storage, so a reload keeps it hidden).
// title and state, each a chip as wide as its text, its title cut off only when the composer's
// column is full, with its dismiss button inside. The chat folder's pull request, which the header
// chip follows live, shows its live state and title. The newest two show and the rest wait behind
// Show more; a dismissed bar stays hidden for this chat (the page's storage, so a reload keeps it
// hidden).
import { useEffect, useRef, useState } from "react";
import { useT, type StringKey } from "../i18n";
import { Icon } from "../icons/Icon";
Expand Down
6 changes: 3 additions & 3 deletions webviews/src/agent-session/acpmux/summary/summary.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions webviews/test/chip-fixed-width.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// l10n-allow-file: test names, not shipped UI.
// A chip, tab or card in the agent pane that cuts its text off with an ellipsis uses the room its
// row has: it truncates only when the row is full, never at a fixed width while the row has space
// (Leo 2026-10-20, the pull request bar above the composer cutting "the label is the …" at 360px).
// This reads the pane's stylesheets: a rule with `text-overflow: ellipsis` may not cap its
// `max-width` at a length (px, ch, em, rem); a percentage or `none` follows the row.
import { readFileSync, readdirSync } from "node:fs";
import path from "node:path";
import { expect, test } from "bun:test";

const ROOT = path.resolve(import.meta.dir, "../src/agent-session/acpmux");
/** Design prototypes and gallery pages are not the app's pane. */
const SKIP = /(^|\/)prototype\/|\.gallery\.css$/;

function sheets(dir: string): string[] {
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) return sheets(full);
return entry.name.endsWith(".css") ? [full] : [];
});
}

/** `selector => max-width` for each ellipsized rule capped at a fixed length. */
function fixedCaps(): string[] {
return sheets(ROOT)
.filter((file) => !SKIP.test(path.relative(ROOT, file)))
.flatMap((file) => {
const css = readFileSync(file, "utf8").replace(/\/\*[\s\S]*?\*\//g, "");
return [...css.matchAll(/([^{}]+)\{([^{}]*)\}/g)].flatMap(([, selector, body]) => {
const cap = /max-width\s*:\s*([^;]+)/.exec(body!)?.[1]?.trim();
if (!cap || !/ellipsis/.test(body!) || !/\d(px|ch|r?em)\b/.test(cap)) return [];
return [`${path.relative(ROOT, file)} ${selector!.trim().replace(/\s+/g, " ")} => ${cap}`];
});
});
}

test("no ellipsized chip, tab or card in the agent pane truncates at a fixed width", () => {
expect(fixedCaps()).toEqual([]);
});