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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
- Fixed the model dropdown in the status bar being unable to shrink below roughly half the panel's width — it was inflating its minimum size to fit the widest previously-seen item, even a stale oversized one left over from a misparsed model list (https://github.com/nbplugins/NetbeansPluginClaudeCodeGui/issues/176)
- Fixed the model dropdown showing full descriptions (including the checkmark and effort text) instead of short model names with newer Claude Code CLI versions that add an extra space after the option number (e.g. "1. Default (recommended)") (https://github.com/nbplugins/NetbeansPluginClaudeCodeGui/issues/176)
- Fixed an unnumbered choice prompt (e.g. the "trust this folder" dialog) showing every line of the surrounding text as a bogus selectable option instead of just the real choices, and losing the "Yes" option entirely by merging it into the "No" option's description
- Fixed ChatGPT Subscription token refresh hanging indefinitely (freezing the session) when a profile's custom HTTPS/HTTP proxy hostname was slow or unreachable to resolve via DNS
- Fixed a choice-dialog prompt (e.g. a Bash-command permission prompt) flickering and repeatedly resizing when Claude's response text re-wrapped to a slightly different width after the dialog first appeared
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.nbplugins</groupId>
<artifactId>netbeans-plugin-claude-code-gui</artifactId>
<version>1.3.34-SNAPSHOT</version>
<version>1.3.36-SNAPSHOT</version>
<packaging>nbm</packaging>
<name>Netbeans Plugin Claude Code GUI</name>
<description>NetBeans plugin that provides a GUI for Claude Code CLI — run claude code sessions directly inside the IDE.</description>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ public record ModelDiscovery(List<String> models, int currentIndex) {}
private static final Pattern VERSION_TAIL_PAT =
Pattern.compile("([A-Z][a-z]+\\s+\\d+\\.\\d+)\\s*$");

/** Matches a bare version-like name, e.g. "Opus 5.5" or "Sonnet 5" (no decimal required). */
private static final Pattern NAME_LOOKS_LIKE_VERSION_PAT =
Pattern.compile("^[A-Z][a-zA-Z]+\\s+\\d+(?:\\.\\d+)?$");

/**
* Parses terminal screen lines and returns the discovered models.
*
Expand All @@ -68,7 +72,12 @@ public ModelDiscovery parse(List<String> lines) {

// Numbered format: "N. ..."
if (trimmed.matches("^\\d+\\..*")) {
String leftPart = trimmed.split("[·\u00b7]", 2)[0];
// Strip the ordinal (and any following whitespace — some CLI versions emit a
// single space, others two) first, so a double-space-after-ordinal quirk can
// never be mistaken for the name/description column boundary below.
String afterOrdinal = trimmed.replaceFirst("^\\d+\\.\\s*", "");
boolean hasSeparator = afterOrdinal.indexOf('·') >= 0;
String leftPart = hasSeparator ? afterOrdinal.split("[·\\u00b7]", 2)[0] : afterOrdinal;
Matcher descMatcher = DESC_PAT.matcher(leftPart);
if (descMatcher.find()) {
String namePart = leftPart.substring(0, descMatcher.start()).trim();
Expand All @@ -77,9 +86,23 @@ public ModelDiscovery parse(List<String> lines) {
.replaceFirst("^\u2714\\s*", "").trim();
// Format 2: "(currently X)"
Matcher currentlyMatcher = CURRENTLY_PAT.matcher(desc);
String modelId = currentlyMatcher.find()
? currentlyMatcher.group(1)
: desc;
String modelId;
if (currentlyMatcher.find()) {
modelId = currentlyMatcher.group(1);
} else if (!hasSeparator && NAME_LOOKS_LIKE_VERSION_PAT.matcher(namePart).matches()) {
// Some newer CLI versions omit the ·-separated version column
// entirely and put the version directly in the name slot (e.g.
// "Opus 5.5 For complex work..."), leaving no version
// string in the description at all. Take the name here only because
// it itself looks like a bare version ("Word N[.M]") — a missing
// separator alone is not enough, since a truncated terminal line can
// also lack one while still being the old "name version · desc"
// shape (see format2-truncated), whose name column never looks like a
// version.
modelId = namePart;
} else {
modelId = desc;
}
if (hasCheck) currentIndex = models.size();
models.add(modelId);
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ public class ClaudeSessionTab extends TopComponent
private static final String CARD_DIFF = "diff";
private static final int DEFAULT_DIFF_HEIGHT = 300;

/**
* Upper bound on {@link #modelCombo}'s minimum width, so a misparsed or unexpectedly long
* model list entry can never prevent the status bar (and therefore the panel) from being
* shrunk. Shorter content still gets its own smaller natural minimum.
*/
private static final int MODEL_COMBO_MIN_WIDTH = 100;

// -------------------------------------------------------------------------
// Persistence fields
// -------------------------------------------------------------------------
Expand Down Expand Up @@ -266,7 +273,18 @@ public ClaudeSessionTab(File dir) {
editModeCombo.setToolTipText("Edit mode");
editModeCombo.addActionListener(e -> onEditModeComboChanged());

modelCombo = new JComboBox<>();
// Minimum width is capped at MODEL_COMBO_MIN_WIDTH — but only when content would
// otherwise demand more; shorter content keeps its own smaller natural minimum, and
// the combo still grows with its content (up to setMaximumSize below) when there's
// room, since preferredSize itself is left untouched. Computed live in
// getMinimumSize() (not snapshotted once) so it always tracks the current content.
modelCombo = new JComboBox<>() {
@Override
public Dimension getMinimumSize() {
Dimension pref = getPreferredSize();
return new Dimension(Math.min(pref.width, MODEL_COMBO_MIN_WIDTH), pref.height);
}
};
modelCombo.setMaximumSize(new Dimension(200, 24));
modelCombo.setToolTipText("Active model");
modelCombo.setEnabled(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.awt.BorderLayout;
import java.awt.Dimension;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JSplitPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
Expand Down Expand Up @@ -158,4 +160,50 @@ void splitPaneAllowsCollapseWhenMinSizeIsZero() throws Exception {
"got " + min.height + " px; large value means resize is blocked (issue #19)");
});
}

/**
* Regression for issue #176: the model dropdown's minimum width must be capped (here at
* 100px, mirroring {@code ClaudeSessionTab.MODEL_COMBO_MIN_WIDTH}) whenever its content
* would otherwise demand more — including a misparsed, oversized entry — so the panel can
* always be shrunk. But the cap must never exceed the combo's own (content-driven, dynamic)
* preferred width, so the combo still grows to show genuinely short content in full, and
* still grows further when the window is widened, up to its {@code setMaximumSize} cap.
*/
@Test
void modelComboMinimumWidthTracksContentButIsCappedAt100() throws Exception {
final int cap = 100;
SwingUtilities.invokeAndWait(() -> {
JComboBox<String> combo = new JComboBox<>() {
@Override
public Dimension getMinimumSize() {
Dimension pref = getPreferredSize();
return new Dimension(Math.min(pref.width, cap), pref.height);
}
};

// Short content: minimum should track the (smaller) preferred width, not the cap.
combo.setModel(new DefaultComboBoxModel<>(new String[]{"Opus 5.5"}));
int shortPrefWidth = combo.getPreferredSize().width;
assertTrue(shortPrefWidth < cap,
"test assumption broken: short item's preferred width (" + shortPrefWidth +
") should be under the cap (" + cap + ")");
assertEquals(shortPrefWidth, combo.getMinimumSize().width,
"minimum width must equal the (smaller) preferred width for short content, " +
"so the combo isn't forced wider than its content needs");

// Long / misparsed content: minimum must be capped at 100, not follow the huge preferred width.
String longItem = "Opus 5.5 ✔ For complex work and everyday tasks describing the model in detail";
combo.setModel(new DefaultComboBoxModel<>(new String[]{longItem}));
int longPrefWidth = combo.getPreferredSize().width;
assertTrue(longPrefWidth > cap,
"test assumption broken: long item's preferred width (" + longPrefWidth +
") should exceed the cap (" + cap + ")");
assertEquals(cap, combo.getMinimumSize().width,
"minimum width must be capped at 100 even though preferred width (" + longPrefWidth +
") is much larger (issue #176) — otherwise the panel can't be shrunk");
assertEquals(longPrefWidth, combo.getPreferredSize().width,
"preferred width itself must stay content-driven (uncapped) so the combo can still " +
"grow to show the full content when the window is widened");
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"models": ["Opus 5.5", "Opus 5.5", "Sonnet 5.5", "Fable 5.1", "Haiku 4.5", "Sonnet 5", "Opus 5", "Fable 5", "Opus 4.8"], "currentIndex": 1}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Select model
Switch between Claude models. Your pick becomes the default for new sessions. For other/previous model names, specify with --model.

1. Default (recommended) Opus 5.5 · Best for everyday, complex tasks
❯ 2. Opus 5.5 ✔ For complex work and everyday tasks
3. Sonnet 5.5 Most efficient for simpler tasks
4. Fable 5.1 For your toughest challenges
5. Haiku 4.5 Fastest for quick answers
6. Sonnet 5 Efficient for routine tasks
7. Opus 5 Best for everyday, complex tasks
8. Fable 5 Most capable for your hardest and longest-running tasks
9. Opus 4.8 Best for everyday, complex tasks

◐ Medium effort (default) ←/→ to adjust

Enter to set as default · s to use this session only · Esc to cancel
Loading