diff --git a/CHANGELOG.md b/CHANGELOG.md index 97bd214a..e7477681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +- Fixed prompt area being collapsible to zero height by dragging the divider - Fixed terminal scrolling to the beginning and locking when a choice/confirmation menu appears (https://github.com/nbclaudecodegui/NetbeansClaudeCodeGui/issues/22) - Added a setting to control whether the choice menu grabs keyboard focus (https://github.com/nbclaudecodegui/NetbeansClaudeCodeGui/issues/20) - Added a global setting to choose the default dock position of the Claude Code session tab (https://github.com/nbclaudecodegui/NetbeansClaudeCodeGui/issues/24) diff --git a/pom.xml b/pom.xml index 7a9b161a..31437b7e 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ io.github.nbclaudecodegui netbeans-claude-code-gui - 0.22.9-SNAPSHOT + 0.22.10-SNAPSHOT nbm Netbeans 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/nbclaudecodegui/ui/ClaudePromptPanel.java b/src/main/java/io/github/nbclaudecodegui/ui/ClaudePromptPanel.java index cbbc76bb..39724074 100644 --- a/src/main/java/io/github/nbclaudecodegui/ui/ClaudePromptPanel.java +++ b/src/main/java/io/github/nbclaudecodegui/ui/ClaudePromptPanel.java @@ -201,6 +201,18 @@ public ClaudePromptPanel(Consumer onSend, }); } + @Override + public java.awt.Dimension getMinimumSize() { + BorderLayout layout = (BorderLayout) getLayout(); + java.awt.Component east = layout.getLayoutComponent(BorderLayout.EAST); + java.awt.Component west = layout.getLayoutComponent(BorderLayout.WEST); + int minH = 0; + if (east != null) minH = Math.max(minH, east.getMinimumSize().height); + if (west != null) minH = Math.max(minH, west.getMinimumSize().height); + java.awt.Insets ins = getInsets(); + return new java.awt.Dimension(0, minH + ins.top + ins.bottom); + } + // ------------------------------------------------------------------------- // Public API // ------------------------------------------------------------------------- diff --git a/src/main/java/io/github/nbclaudecodegui/ui/ClaudeSessionTab.java b/src/main/java/io/github/nbclaudecodegui/ui/ClaudeSessionTab.java index 33c7b7ae..5a7f76b7 100644 --- a/src/main/java/io/github/nbclaudecodegui/ui/ClaudeSessionTab.java +++ b/src/main/java/io/github/nbclaudecodegui/ui/ClaudeSessionTab.java @@ -717,16 +717,17 @@ private void showChatLayout(JediTermWidget widget) { southCardLayout = new CardLayout(); southCard = new JPanel(southCardLayout) { /** - * Returns (0, 0) so that JSplitPane can collapse the bottom panel - * freely when the tab is docked as a narrow side panel. - * - * Without this override, getMinimumSize() delegates to the visible - * child (e.g. promptPanel with a 3-row DecoratedTextArea + buttons), - * which reports a tall minimum height (~600 px). JSplitPane enforces - * that minimum, causing the divider to bounce back when the user tries - * to shrink the tab below that height (issue #19). + * Returns (0, 0) for non-prompt cards so that JSplitPane can collapse + * the bottom panel freely when the tab is docked as a narrow side panel + * (issue #19). For the prompt card, delegates to the visible child's + * minimum height so the divider cannot be dragged below the button column. */ @Override public java.awt.Dimension getMinimumSize() { + if (CARD_PROMPT.equals(activeCard)) { + for (java.awt.Component c : getComponents()) { + if (c.isVisible()) return new java.awt.Dimension(0, c.getMinimumSize().height); + } + } return new java.awt.Dimension(0, 0); } @Override public java.awt.Dimension getPreferredSize() { diff --git a/src/test/java/io/github/nbclaudecodegui/ui/ClaudeSessionTabMinSizeTest.java b/src/test/java/io/github/nbclaudecodegui/ui/ClaudeSessionTabMinSizeTest.java index d2637a10..63f5a81d 100644 --- a/src/test/java/io/github/nbclaudecodegui/ui/ClaudeSessionTabMinSizeTest.java +++ b/src/test/java/io/github/nbclaudecodegui/ui/ClaudeSessionTabMinSizeTest.java @@ -1,5 +1,6 @@ package io.github.nbclaudecodegui.ui; +import java.awt.BorderLayout; import java.awt.Dimension; import javax.swing.JSplitPane; import javax.swing.JPanel; @@ -8,49 +9,130 @@ import static org.junit.jupiter.api.Assertions.*; /** - * Regression test for issue #19: the session tab must be resizable below - * ~600 px when docked as a side panel. + * Regression tests for prompt-area resize behaviour. * - *

Root cause: {@code southCard.getMinimumSize()} was delegating to the - * visible child component (e.g. promptPanel with a 3-row text area + buttons), - * which reported a tall minimum height. {@link JSplitPane} enforces the - * bottom component's minimum size, so the divider bounced back when the user - * tried to shrink the tab below that height. + *

Issue #19: the session tab must be resizable below ~600 px when docked as + * a side panel — fixed by returning (0, 0) from {@code southCard.getMinimumSize()}. * - *

Fix: {@code southCard.getMinimumSize()} now always returns (0, 0), - * letting JSplitPane collapse the bottom panel freely. + *

Follow-up fix (scroll-lock): the prompt area must not be collapsible to + * zero height — fixed by having {@code ClaudePromptPanel.getMinimumSize()} + * return the height of its button columns, and by having {@code southCard} + * delegate to the visible child's minimum when {@code CARD_PROMPT} is active. */ class ClaudeSessionTabMinSizeTest { /** - * Verifies that {@code southCard}'s minimum size is (0, 0) so that - * JSplitPane does not enforce a large lower bound on the tab height. + * Verifies that {@code southCard}'s minimum size is (0, 0) when a non-prompt + * card is active (e.g. CARD_CHOICE), so JSplitPane does not enforce a large + * lower bound on the tab height (issue #19). */ @Test - void southCardMinimumSizeIsZero() throws Exception { + void southCardMinimumSizeIsZeroForChoiceCard() throws Exception { SwingUtilities.invokeAndWait(() -> { - // Simulate the southCard construction from showChatLayout(): - // an anonymous JPanel subclass with getMinimumSize() returning (0,0). + String[] activeCard = {"choice"}; java.awt.CardLayout cardLayout = new java.awt.CardLayout(); JPanel southCard = new JPanel(cardLayout) { @Override public Dimension getMinimumSize() { + if ("prompt".equals(activeCard[0])) { + for (java.awt.Component c : getComponents()) { + if (c.isVisible()) return new Dimension(0, c.getMinimumSize().height); + } + } return new Dimension(0, 0); } }; - // Add a child with a large preferred/minimum size (mirrors promptPanel). - JPanel tallChild = new JPanel() { + JPanel choiceChild = new JPanel() { @Override public Dimension getMinimumSize() { return new Dimension(400, 600); } @Override public Dimension getPreferredSize() { return new Dimension(400, 600); } }; - southCard.add(tallChild, "prompt"); + southCard.add(choiceChild, "choice"); + cardLayout.show(southCard, "choice"); + + Dimension min = southCard.getMinimumSize(); + assertEquals(0, min.width, "southCard min width must be 0 for choice card (issue #19)"); + assertEquals(0, min.height, "southCard min height must be 0 for choice card (issue #19)"); + }); + } + + /** + * Verifies that {@code southCard}'s minimum height is driven by the visible + * child when {@code CARD_PROMPT} is active — so the divider cannot be + * dragged below the button column height. + */ + @Test + void southCardMinimumHeightIsFromChildWhenPromptCardActive() throws Exception { + SwingUtilities.invokeAndWait(() -> { + String[] activeCard = {"prompt"}; + java.awt.CardLayout cardLayout = new java.awt.CardLayout(); + JPanel southCard = new JPanel(cardLayout) { + @Override public Dimension getMinimumSize() { + if ("prompt".equals(activeCard[0])) { + for (java.awt.Component c : getComponents()) { + if (c.isVisible()) return new Dimension(0, c.getMinimumSize().height); + } + } + return new Dimension(0, 0); + } + }; + + JPanel promptChild = new JPanel() { + @Override public Dimension getMinimumSize() { return new Dimension(400, 42); } + @Override public Dimension getPreferredSize() { return new Dimension(400, 100); } + }; + southCard.add(promptChild, "prompt"); cardLayout.show(southCard, "prompt"); Dimension min = southCard.getMinimumSize(); - assertEquals(0, min.width, - "southCard minimum width must be 0 so JSplitPane can resize freely (issue #19)"); - assertEquals(0, min.height, - "southCard minimum height must be 0 so JSplitPane can resize freely (issue #19)"); + assertEquals(0, min.width, "southCard min width must be 0 even for prompt card"); + assertEquals(42, min.height, "southCard min height must match button column height when prompt card active"); + }); + } + + /** + * Verifies that {@code ClaudePromptPanel.getMinimumSize()} returns a height + * driven by the EAST/WEST button columns, not the CENTER scroll pane. + */ + @Test + void promptPanel_minimumHeight_isDrivenByButtonColumns() throws Exception { + SwingUtilities.invokeAndWait(() -> { + JPanel promptPanel = new JPanel(new BorderLayout()) { + @Override + public Dimension getMinimumSize() { + BorderLayout layout = (BorderLayout) getLayout(); + java.awt.Component east = layout.getLayoutComponent(BorderLayout.EAST); + java.awt.Component west = layout.getLayoutComponent(BorderLayout.WEST); + int minH = 0; + if (east != null) minH = Math.max(minH, east.getMinimumSize().height); + if (west != null) minH = Math.max(minH, west.getMinimumSize().height); + java.awt.Insets ins = getInsets(); + return new Dimension(0, minH + ins.top + ins.bottom); + } + }; + + // CENTER: large textarea (should NOT drive minimum) + JPanel center = new JPanel() { + @Override public Dimension getMinimumSize() { return new Dimension(400, 600); } + }; + // EAST: button column + JPanel east = new JPanel() { + @Override public Dimension getMinimumSize() { return new Dimension(30, 55); } + }; + // WEST: button column + JPanel west = new JPanel() { + @Override public Dimension getMinimumSize() { return new Dimension(30, 40); } + }; + + promptPanel.add(center, BorderLayout.CENTER); + promptPanel.add(east, BorderLayout.EAST); + promptPanel.add(west, BorderLayout.WEST); + + Dimension min = promptPanel.getMinimumSize(); + assertEquals(0, min.width, "promptPanel minimum width must be 0"); + assertTrue(min.height >= 55, + "promptPanel minimum height must be >= east button column height (55), got " + min.height); + assertTrue(min.height < 600, + "promptPanel minimum height must not be driven by CENTER textarea (600), got " + min.height); }); }