Skip to content

Commit 2afa284

Browse files
Joelclaude
andcommitted
Fix widget routing infinite loop and room switching
Three critical fixes for widget routing: 1. Infinite loop prevention (MainWidget.ts) - Add guard to switchContentView() tracking currentViewType/entityId - Skip re-render when already showing the same content - Prevents cascading events from triggering infinite widget recreation 2. Remove duplicate event emission (RoomListWidget.ts) - Remove direct content:opened emit from selectRoom() - The collaboration/content/open command already emits with proper contentItemId - Reduces event noise and potential race conditions 3. Critical pageState fix (MainWidget.ts) - ROOM_SELECTED handler now calls pageState.setContent() BEFORE switchContentView() - ChatWidget reads from pageState as priority 1, was loading stale room - Now room switching correctly updates both tab AND chat content Result: Clicking rooms in sidebar now properly switches tabs and content without infinite loops or stale data. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 00f3b0a commit 2afa284

File tree

6 files changed

+29
-15
lines changed

6 files changed

+29
-15
lines changed

src/debug/jtag/generated-command-schemas.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"generated": "2026-01-06T21:38:05.260Z",
2+
"generated": "2026-01-06T22:05:05.706Z",
33
"version": "1.0.0",
44
"commands": [
55
{

src/debug/jtag/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/debug/jtag/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@continuum/jtag",
3-
"version": "1.0.6888",
3+
"version": "1.0.6890",
44
"description": "Global CLI debugging system for any Node.js project. Install once globally, use anywhere: npm install -g @continuum/jtag",
55
"config": {
66
"active_example": "widget-ui",

src/debug/jtag/shared/version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
* DO NOT EDIT MANUALLY
44
*/
55

6-
export const VERSION = '1.0.6888';
6+
export const VERSION = '1.0.6890';
77
export const PACKAGE_NAME = '@continuum/jtag';

src/debug/jtag/widgets/chat/room-list/RoomListWidget.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -208,21 +208,16 @@ export class RoomListWidget extends EntityScrollerWidget<RoomEntity> {
208208

209209
// Emit room selection IMMEDIATELY so ChatWidget switches fast
210210
// Include uniqueId for human-readable URL building
211+
// NOTE: MainWidget handles view switching via ROOM_SELECTED
212+
// NOTE: Tab creation happens via collaboration/content/open command below
213+
// DO NOT emit content:opened here - the command will emit it with proper contentItemId
211214
Events.emit(UI_EVENTS.ROOM_SELECTED, {
212215
roomId,
213216
roomName,
214217
uniqueId: roomEntity.uniqueId || roomEntity.name || roomId // Prefer uniqueId for URLs
215218
});
216219

217-
// Emit content:opened for MainWidget tab update (optimistic)
218-
// Use uniqueId for human-readable URLs
219-
Events.emit('content:opened', {
220-
contentType: 'chat',
221-
entityId: roomEntity.uniqueId || roomId, // Use uniqueId for content state
222-
title: roomName
223-
});
224-
225-
// Persist to server in BACKGROUND (don't block UI)
220+
// Persist to server in BACKGROUND - command emits content:opened with proper data
226221
const userId = this.userState?.userId;
227222
if (userId) {
228223
Commands.execute<ContentOpenParams, ContentOpenResult>('collaboration/content/open', {

src/debug/jtag/widgets/main/MainWidget.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ export class MainWidget extends BaseWidget {
3131
private currentContent: ContentInfo | null = null;
3232
private contentStateAdapter: PositronContentStateAdapter;
3333

34+
// Guard against infinite re-render loops
35+
private currentViewType: string | null = null;
36+
private currentViewEntityId: string | undefined = undefined;
37+
3438
constructor() {
3539
super({
3640
widgetName: 'MainWidget',
@@ -327,9 +331,19 @@ export class MainWidget extends BaseWidget {
327331
* Emits RIGHT_PANEL_CONFIGURE to update right panel based on content type's layout
328332
*/
329333
private switchContentView(contentType: string, entityId?: string): void {
334+
// GUARD: Prevent infinite re-render loops by checking if already showing this content
335+
if (this.currentViewType === contentType && this.currentViewEntityId === entityId) {
336+
console.log(`🔄 MainPanel: Already showing ${contentType}/${entityId || 'default'}, skipping re-render`);
337+
return;
338+
}
339+
330340
const contentView = this.shadowRoot?.querySelector('.content-view');
331341
if (!contentView) return;
332342

343+
// Track what we're rendering to prevent loops
344+
this.currentViewType = contentType;
345+
this.currentViewEntityId = entityId;
346+
333347
const widgetTag = getWidgetForType(contentType);
334348

335349
// Create widget element with entity context
@@ -639,7 +653,7 @@ export class MainWidget extends BaseWidget {
639653

640654
// IMPORTANT: Also listen for ROOM_SELECTED as reliable backup
641655
// RoomListWidget emits this and it definitely works (sidebar highlights change)
642-
Events.subscribe(UI_EVENTS.ROOM_SELECTED, (data: { roomId: string; roomName: string; uniqueId?: string }) => {
656+
Events.subscribe(UI_EVENTS.ROOM_SELECTED, async (data: { roomId: string; roomName: string; uniqueId?: string }) => {
643657
console.log('📋 MainPanel: Received ROOM_SELECTED event:', data.roomName);
644658

645659
// Only switch to chat if we're currently viewing chat content
@@ -656,6 +670,11 @@ export class MainWidget extends BaseWidget {
656670
const newPath = buildContentPath('chat', urlIdentifier);
657671
this.updateUrl(newPath);
658672

673+
// CRITICAL: Set pageState BEFORE creating widget - ChatWidget reads from pageState
674+
// Resolve entity for proper display name
675+
const resolved = await RoutingService.resolve('chat', urlIdentifier);
676+
pageState.setContent('chat', urlIdentifier, resolved || { id: data.roomId, displayName: data.roomName, uniqueId: urlIdentifier });
677+
659678
// Switch to the selected chat room (use uniqueId for content view)
660679
this.switchContentView('chat', urlIdentifier);
661680

0 commit comments

Comments
 (0)