diff --git a/CHANGELOG.md b/CHANGELOG.md index 9319dc6..1dd03b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/pom.xml b/pom.xml index cf0d2ed..8be77d2 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.github.nbplugins netbeans-plugin-claude-code-gui - 1.3.34-SNAPSHOT + 1.3.36-SNAPSHOT nbm Netbeans Plugin Claude Code GUI NetBeans plugin that provides a GUI for Claude Code CLI — run claude code sessions directly inside the IDE. diff --git a/src/main/java/io/github/nbplugins/claudecodegui/process/ModelMenuParser.java b/src/main/java/io/github/nbplugins/claudecodegui/process/ModelMenuParser.java index 16a1267..2eee8cd 100644 --- a/src/main/java/io/github/nbplugins/claudecodegui/process/ModelMenuParser.java +++ b/src/main/java/io/github/nbplugins/claudecodegui/process/ModelMenuParser.java @@ -51,6 +51,10 @@ public record ModelDiscovery(List 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. * @@ -68,7 +72,12 @@ public ModelDiscovery parse(List 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(); @@ -77,9 +86,23 @@ public ModelDiscovery parse(List 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; diff --git a/src/main/java/io/github/nbplugins/claudecodegui/ui/ClaudeSessionTab.java b/src/main/java/io/github/nbplugins/claudecodegui/ui/ClaudeSessionTab.java index 8094478..f069d4a 100644 --- a/src/main/java/io/github/nbplugins/claudecodegui/ui/ClaudeSessionTab.java +++ b/src/main/java/io/github/nbplugins/claudecodegui/ui/ClaudeSessionTab.java @@ -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 // ------------------------------------------------------------------------- @@ -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); diff --git a/src/test/java/io/github/nbplugins/claudecodegui/ui/ClaudeSessionTabMinSizeTest.java b/src/test/java/io/github/nbplugins/claudecodegui/ui/ClaudeSessionTabMinSizeTest.java index 332aaf2..60d3700 100644 --- a/src/test/java/io/github/nbplugins/claudecodegui/ui/ClaudeSessionTabMinSizeTest.java +++ b/src/test/java/io/github/nbplugins/claudecodegui/ui/ClaudeSessionTabMinSizeTest.java @@ -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; @@ -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 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"); + }); + } } diff --git a/src/test/resources/io/github/nbplugins/claudecodegui/process/model-menu-parser/format3-double-space-ordinal.expected.json b/src/test/resources/io/github/nbplugins/claudecodegui/process/model-menu-parser/format3-double-space-ordinal.expected.json new file mode 100644 index 0000000..603c5ba --- /dev/null +++ b/src/test/resources/io/github/nbplugins/claudecodegui/process/model-menu-parser/format3-double-space-ordinal.expected.json @@ -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} diff --git a/src/test/resources/io/github/nbplugins/claudecodegui/process/model-menu-parser/format3-double-space-ordinal.src.txt b/src/test/resources/io/github/nbplugins/claudecodegui/process/model-menu-parser/format3-double-space-ordinal.src.txt new file mode 100644 index 0000000..5754b26 --- /dev/null +++ b/src/test/resources/io/github/nbplugins/claudecodegui/process/model-menu-parser/format3-double-space-ordinal.src.txt @@ -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