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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- Fixed model combo not becoming active when a session starts with a blocking dialog (e.g. "Resuming session" prompt on --continue/--resume startup)
- Improved keyboard navigation in choice menus: arrow keys, Tab, Shift+Tab
- Added terminal font and font size settings (Tools → Options → Claude Code → General); defaults to auto-detection preferring Adwaita Mono for full Unicode coverage of Claude Code TUI symbols (https://github.com/nbclaudecodegui/NetbeansClaudeCodeGui/issues/18)
- Reorganized General and Advanced settings tabs: moved technical settings (hang timeout, MCP port, Enable MCP integration, session list limit, debug mode) to Advanced
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.nbclaudecodegui</groupId>
<artifactId>netbeans-claude-code-gui</artifactId>
<version>0.22.31-SNAPSHOT</version>
<version>0.22.32-SNAPSHOT</version>
<packaging>nbm</packaging>
<name>Netbeans 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 @@ -48,7 +48,7 @@
* direct widget references after {@link #startProcess} returns.</li>
* </ul>
*/
public final class ClaudeSessionController {
public class ClaudeSessionController {

// -------------------------------------------------------------------------
// Fields
Expand Down Expand Up @@ -763,7 +763,7 @@ public void switchModel(int index) {
* Retries up to {@value #MAX_MODEL_DISCOVERY_ATTEMPTS} times if the screen
* returns no models.
*/
private void discoverModels() {
void discoverModels() {
if (connector == null || modelComboPopulated || modelDiscoveryInProgress) return;
if (modelDiscoveryAttempts >= MAX_MODEL_DISCOVERY_ATTEMPTS) return;
modelComboPopulated = true;
Expand Down Expand Up @@ -923,9 +923,16 @@ void pollScreenState() {
model.setLifecycle(SessionLifecycle.READY);
}

// On first READY: discover models and detect initial edit mode
// On first READY: discover models and detect initial edit mode.
// Defer discovery while any blocking dialog is visible — e.g. the
// "Resuming session" prompt shown on --continue/--resume startup.
// Sending /model while Claude is blocked by such a dialog causes the
// model menu to appear only after the dialog is dismissed, by which
// time discovery has already timed out and given up.
if (model.getLifecycle() == SessionLifecycle.READY
&& !modelComboPopulated && !modelDiscoveryInProgress) {
&& !modelComboPopulated && !modelDiscoveryInProgress
&& model.getActiveChoiceMenu() == null
&& screenContentDetector.detectChoiceMenu(lines).isEmpty()) {
discoverModels();
detectAndApplyInitialEditMode(lines);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,49 @@ void hangNotDetectedWhenWithinTimeout() {
assertNull(callbackCommand.get(), "Hang callback should NOT fire when elapsed < timeout");
}

// -------------------------------------------------------------------------
// discoverModels deferred when blocking dialog is visible at READY
// -------------------------------------------------------------------------

/**
* Regression: when Claude starts with --continue/--resume, it may show a
* "Resuming session" dialog before the input prompt. If discoverModels() is
* triggered while this dialog is visible, it sends /model but Claude cannot
* process it yet (blocked by the dialog). Discovery times out, the combo
* stays unpopulated, and MAX_MODEL_DISCOVERY_ATTEMPTS prevents any retry.
*
* Fix: pollScreenState must skip discoverModels() while any choice menu is
* visible on screen or already shown to the user.
*/
@Test
void pollScreenState_doesNotStartDiscoveryWhenBlockingMenuIsPresent() {
List<String> blockingMenuScreen = Arrays.asList(
"Resuming the full session will consume a substantial portion of your usage limits.",
"❯ 1. Resume from full history",
" 2. Resume from summary",
"Enter to confirm · Esc to cancel"
);

java.util.concurrent.atomic.AtomicInteger discoveryCallCount =
new java.util.concurrent.atomic.AtomicInteger(0);

ClaudeSessionModel m = new ClaudeSessionModel();
ClaudeSessionController c = new ClaudeSessionController(m, () -> blockingMenuScreen) {
@Override
void discoverModels() {
discoveryCallCount.incrementAndGet();
}
};

m.setLifecycle(SessionLifecycle.READY);
c.pollScreenState();

assertEquals(0, discoveryCallCount.get(),
"discoverModels must not be called while a blocking dialog is visible on screen");
assertNotNull(m.getActiveChoiceMenu(),
"Blocking dialog must be shown to the user while discovery is deferred");
}

// -------------------------------------------------------------------------
// No-op listener base class
// -------------------------------------------------------------------------
Expand Down
Loading