diff --git a/pom.xml b/pom.xml
index 55df72d..63d2ccb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
io.github.nbplugins
netbeans-plugin-claude-code-gui
- 1.3.38-SNAPSHOT
+ 1.3.39-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/controller/ClaudeSessionController.java b/src/main/java/io/github/nbplugins/claudecodegui/controller/ClaudeSessionController.java
index 839c9c6..ae40e52 100644
--- a/src/main/java/io/github/nbplugins/claudecodegui/controller/ClaudeSessionController.java
+++ b/src/main/java/io/github/nbplugins/claudecodegui/controller/ClaudeSessionController.java
@@ -1454,26 +1454,40 @@ private void applyDetectedChoiceMenu(ChoiceMenuModel newMenu, String logTag) {
/**
* Returns {@code true} when {@code newMenu} is just a shorter window onto the same
- * scrolling list as {@code current} (same question, options are a strict prefix).
+ * scrolling list as {@code current}: same question (or both "question" lines are
+ * scroll indicators such as {@code ↑ 8. Fable 5}, which change with the scroll
+ * position) and every option matches an option of {@code current} by response and
+ * display, with fewer options in total.
*
*
Scrolling pickers such as {@code /model} draw as many items as fit the terminal.
- * The choice panel shrinks the terminal, which makes Claude draw fewer items, which
- * shrinks the panel, which grows the terminal again — an endless flicker. Keeping
- * the larger option list breaks that loop.
+ * The choice panel shrinks the terminal, which makes Claude draw fewer items (and
+ * move the scroll window), which shrinks the panel, which grows the terminal again —
+ * an endless flicker. Keeping the larger option list breaks that loop.
*/
static boolean isShrunkViewOf(ChoiceMenuModel newMenu, ChoiceMenuModel current) {
List n = newMenu.options();
List c = current.options();
- if (!newMenu.text().equals(current.text()) || n.size() >= c.size()) return false;
- for (int i = 0; i < n.size(); i++) {
- if (!n.get(i).display().equals(c.get(i).display())
- || !n.get(i).response().equals(c.get(i).response())) {
- return false;
+ if (n.size() >= c.size()) return false;
+ boolean sameText = newMenu.text().equals(current.text())
+ || (isScrollIndicator(newMenu.text()) && isScrollIndicator(current.text()));
+ if (!sameText) return false;
+ for (ChoiceMenuModel.Option no : n) {
+ boolean found = false;
+ for (ChoiceMenuModel.Option co : c) {
+ if (no.display().equals(co.display()) && no.response().equals(co.response())) {
+ found = true;
+ break;
+ }
}
+ if (!found) return false;
}
return true;
}
+ private static boolean isScrollIndicator(String text) {
+ return text.startsWith("\u2191") || text.startsWith("\u2193");
+ }
+
private static boolean sameMenu(ChoiceMenuModel a, ChoiceMenuModel b) {
return a.text().equals(b.text()) && optionsEqual(a.options(), b.options());
}
diff --git a/src/test/java/io/github/nbplugins/claudecodegui/controller/ClaudeSessionControllerTest.java b/src/test/java/io/github/nbplugins/claudecodegui/controller/ClaudeSessionControllerTest.java
index b91b6dc..7da432e 100644
--- a/src/test/java/io/github/nbplugins/claudecodegui/controller/ClaudeSessionControllerTest.java
+++ b/src/test/java/io/github/nbplugins/claudecodegui/controller/ClaudeSessionControllerTest.java
@@ -68,6 +68,29 @@ void isShrunkViewOf_differentTextOrOptions_false() {
assertFalse(ClaudeSessionController.isShrunkViewOf(menu("Select model", "a", "x", "c"), full));
}
+ /** Menu whose options are numbered from {@code first} (a scrolled window of a long list). */
+ private static ChoiceMenuModel scrolled(String text, int first, String... displays) {
+ List opts = new java.util.ArrayList<>();
+ for (int i = 0; i < displays.length; i++) {
+ opts.add(new ChoiceMenuModel.Option(displays[i], String.valueOf(first + i)));
+ }
+ return new ChoiceMenuModel(text, opts, 0);
+ }
+
+ @Test
+ void isShrunkViewOf_scrolledWindowWithDifferentScrollIndicator_true() {
+ ChoiceMenuModel full = scrolled("\u2191 8. Fable 5", 9, "a", "b", "c", "d", "e");
+ ChoiceMenuModel shrunk = scrolled("\u2191 10. b", 11, "c", "d", "e");
+ assertTrue(ClaudeSessionController.isShrunkViewOf(shrunk, full));
+ }
+
+ @Test
+ void isShrunkViewOf_scrolledWindowMismatchingOption_false() {
+ ChoiceMenuModel full = scrolled("\u2191 8. Fable 5", 9, "a", "b", "c", "d", "e");
+ assertFalse(ClaudeSessionController.isShrunkViewOf(scrolled("\u2191 10. b", 11, "c", "x", "e"), full));
+ assertFalse(ClaudeSessionController.isShrunkViewOf(scrolled("Question", 11, "c", "d", "e"), full));
+ }
+
@Test
void onClaudeIdleSetsLifecycleToReady() {
model.setLifecycle(SessionLifecycle.WORKING);