mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 06:42:44 +00:00
Merged via /review-pr -> /prepare-pr -> /merge-pr.
Prepared head SHA: 19c18977e0
Co-authored-by: robbyczgw-cla <239660374+robbyczgw-cla@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete
This commit is contained in:
@@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Security/Discovery: stop treating Bonjour TXT records as authoritative routing (prefer resolved service endpoints) and prevent discovery from overriding stored TLS pins; autoconnect now requires a previously trusted gateway. Thanks @simecek.
|
- Security/Discovery: stop treating Bonjour TXT records as authoritative routing (prefer resolved service endpoints) and prevent discovery from overriding stored TLS pins; autoconnect now requires a previously trusted gateway. Thanks @simecek.
|
||||||
- macOS: hard-limit unkeyed `openclaw://agent` deep links and ignore `deliver` / `to` / `channel` unless a valid unattended key is provided. Thanks @Cillian-Collins.
|
- macOS: hard-limit unkeyed `openclaw://agent` deep links and ignore `deliver` / `to` / `channel` unless a valid unattended key is provided. Thanks @Cillian-Collins.
|
||||||
- Plugins: suppress false duplicate plugin id warnings when the same extension is discovered via multiple paths (config/workspace/global vs bundled), while still warning on genuine duplicates. (#16222) Thanks @shadril238.
|
- Plugins: suppress false duplicate plugin id warnings when the same extension is discovered via multiple paths (config/workspace/global vs bundled), while still warning on genuine duplicates. (#16222) Thanks @shadril238.
|
||||||
|
- TUI: use available terminal width for session name display in searchable select lists. (#16238) Thanks @robbyczgw-cla.
|
||||||
- Security/Voice Call: require valid Twilio webhook signatures even when ngrok free tier loopback compatibility mode is enabled. Thanks @p80n-sec.
|
- Security/Voice Call: require valid Twilio webhook signatures even when ngrok free tier loopback compatibility mode is enabled. Thanks @p80n-sec.
|
||||||
- Security/Google Chat: deprecate `users/<email>` allowlists (treat `users/...` as immutable user id only); keep raw email allowlists for usability. Thanks @vincentkoc.
|
- Security/Google Chat: deprecate `users/<email>` allowlists (treat `users/...` as immutable user id only); keep raw email allowlists for usability. Thanks @vincentkoc.
|
||||||
- Security/Google Chat: reject ambiguous shared-path webhook routing when multiple webhook targets verify successfully (prevents cross-account policy-context misrouting). Thanks @vincentkoc.
|
- Security/Google Chat: reject ambiguous shared-path webhook routing when multiple webhook targets verify successfully (prevents cross-account policy-context misrouting). Thanks @vincentkoc.
|
||||||
|
|||||||
@@ -38,6 +38,16 @@ describe("SearchableSelectList", () => {
|
|||||||
expect(output[0]).toContain("search");
|
expect(output[0]).toContain("search");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("does not truncate long labels on wide terminals when description is present", () => {
|
||||||
|
const tail = "__TAIL__";
|
||||||
|
const longLabel = `session-${"x".repeat(40)}${tail}`; // > 30 chars; tail would be lost before PR
|
||||||
|
const items = [{ value: longLabel, label: longLabel, description: "desc" }];
|
||||||
|
const list = new SearchableSelectList(items, 5, mockTheme);
|
||||||
|
|
||||||
|
const output = list.render(120).join("\n");
|
||||||
|
expect(output).toContain(tail);
|
||||||
|
});
|
||||||
|
|
||||||
it("filters items when typing", () => {
|
it("filters items when typing", () => {
|
||||||
const list = new SearchableSelectList(testItems, 5, mockTheme);
|
const list = new SearchableSelectList(testItems, 5, mockTheme);
|
||||||
|
|
||||||
|
|||||||
@@ -219,20 +219,28 @@ export class SearchableSelectList implements Component {
|
|||||||
const displayValue = this.getItemLabel(item);
|
const displayValue = this.getItemLabel(item);
|
||||||
|
|
||||||
if (item.description && width > 40) {
|
if (item.description && width > 40) {
|
||||||
const maxValueWidth = Math.min(30, width - prefixWidth - 4);
|
const minDescriptionWidth = 12;
|
||||||
const truncatedValue = truncateToWidth(displayValue, maxValueWidth, "");
|
const spacingWidth = 2;
|
||||||
const valueText = this.highlightMatch(truncatedValue, query);
|
const availableWidth = Math.max(1, width - prefixWidth - 2);
|
||||||
const spacingWidth = Math.max(1, 32 - visibleWidth(valueText));
|
|
||||||
const spacing = " ".repeat(spacingWidth);
|
if (availableWidth > minDescriptionWidth + spacingWidth + 1) {
|
||||||
const descriptionStart = prefixWidth + visibleWidth(valueText) + spacing.length;
|
const maxValueWidth = availableWidth - minDescriptionWidth - spacingWidth;
|
||||||
const remainingWidth = width - descriptionStart - 2;
|
const truncatedValue = truncateToWidth(displayValue, maxValueWidth, "");
|
||||||
if (remainingWidth > 10) {
|
const valueText = this.highlightMatch(truncatedValue, query);
|
||||||
const truncatedDesc = truncateToWidth(item.description, remainingWidth, "");
|
|
||||||
// Highlight plain text first, then apply theme styling to avoid corrupting ANSI codes
|
const usedByValue = visibleWidth(valueText);
|
||||||
const highlightedDesc = this.highlightMatch(truncatedDesc, query);
|
const remainingWidth = availableWidth - usedByValue;
|
||||||
const descText = isSelected ? highlightedDesc : this.theme.description(highlightedDesc);
|
|
||||||
const line = `${prefix}${valueText}${spacing}${descText}`;
|
if (remainingWidth > spacingWidth + 1) {
|
||||||
return isSelected ? this.theme.selectedText(line) : line;
|
const descriptionWidth = remainingWidth - spacingWidth;
|
||||||
|
const spacing = " ".repeat(spacingWidth);
|
||||||
|
const truncatedDesc = truncateToWidth(item.description, descriptionWidth, "");
|
||||||
|
// Highlight plain text first, then apply theme styling to avoid corrupting ANSI codes
|
||||||
|
const highlightedDesc = this.highlightMatch(truncatedDesc, query);
|
||||||
|
const descText = isSelected ? highlightedDesc : this.theme.description(highlightedDesc);
|
||||||
|
const line = `${prefix}${valueText}${spacing}${descText}`;
|
||||||
|
return isSelected ? this.theme.selectedText(line) : line;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user