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
3 changes: 3 additions & 0 deletions .openhands_instructions
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Read and follow `/AGENTS.md` — it is the single source of truth for project rules, coding standards, and design constraints.

Do not commit documentation files, standalone test scripts, or debugging artifacts. Only commit production code and its corresponding spec files.
59 changes: 58 additions & 1 deletion src/app/shared/focus.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,21 +56,30 @@ describe('FocusService', () => {
const ideasSignal = signal<Idea[]>([makeIdea('id-1')]);
const ticketsSignal = signal<JiraTicket[]>([makeTicket('tk-1')]);
const pullRequestsSignal = signal<any[]>([]);
const ticketsLoadingSignal = signal(false);
const pullRequestsLoadingSignal = signal(false);

beforeEach(() => {
localStorage.clear();
todosSignal.set([makeTodo('td-1')]);
ideasSignal.set([makeIdea('id-1')]);
ticketsSignal.set([makeTicket('tk-1')]);
pullRequestsSignal.set([]);
ticketsLoadingSignal.set(false);
pullRequestsLoadingSignal.set(false);
TestBed.configureTestingModule({
providers: [
FocusService,
{ provide: TodoService, useValue: { todos: todosSignal } },
{ provide: IdeaService, useValue: { ideas: ideasSignal } },
{
provide: WorkspaceService,
useValue: { tickets: ticketsSignal, pullRequests: pullRequestsSignal },
useValue: {
tickets: ticketsSignal,
pullRequests: pullRequestsSignal,
ticketsLoading: ticketsLoadingSignal,
pullRequestsLoading: pullRequestsLoadingSignal
},
},
],
});
Expand Down Expand Up @@ -133,4 +142,52 @@ describe('FocusService', () => {
JSON.stringify({ id: 'td-1', type: 'todo' }),
);
});

it('preserves focus when data is not loaded yet', () => {
// Set focus on a ticket
service.setFocus({ id: 'tk-1', type: 'ticket' });
TestBed.tick();

// Verify focus is set
expect(service.focusTarget()).toEqual({ id: 'tk-1', type: 'ticket' });
expect(service.focusedItem()).toEqual(makeTicket('tk-1'));

// Simulate data being cleared (but loading is complete)
ticketsSignal.set([]);
TestBed.tick();

// Focus should be cleared because data is loaded and item doesn't exist
expect(service.focusTarget()).toBeNull();

// Now simulate the actual issue: focus loaded from storage but data not loaded yet
ticketsLoadingSignal.set(true);

// Set focus again and clear data
service.setFocus({ id: 'tk-1', type: 'ticket' });
ticketsSignal.set([]);
TestBed.tick();

// Focus should be preserved because data is still loading
expect(service.focusTarget()).toEqual({ id: 'tk-1', type: 'ticket' });
expect(service.focusedItem()).toBeNull();

// Now mark data as loaded
ticketsLoadingSignal.set(false);
TestBed.tick();

// Focus should be cleared because data is loaded and item doesn't exist
expect(service.focusTarget()).toBeNull();
});

it('clears focus when item no longer exists after data is loaded', () => {
service.setFocus({ id: 'tk-1', type: 'ticket' });
TestBed.tick();

// Remove the ticket from data
ticketsSignal.set([]);
TestBed.tick();

// Focus should be cleared because data is loaded and item doesn't exist
expect(service.focusTarget()).toBeNull();
});
});
14 changes: 13 additions & 1 deletion src/app/shared/focus.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class FocusService {
effect(() => {
const item = this.focusedItem();
const target = this.focusTarget();
if (target && !item) {
if (target && !item && this.isDataLoadedForTarget(target)) {
this.focusTarget.set(null);
}
});
Expand Down Expand Up @@ -64,6 +64,18 @@ export class FocusService {
}
}

private isDataLoadedForTarget(target: FocusTarget): boolean {
switch (target.type) {
case 'ticket':
return !this.data.ticketsLoading();
case 'pr':
return !this.data.pullRequestsLoading();
case 'todo':
case 'idea':
return true;
}
}

private loadFromStorage(): FocusTarget | null {
try {
const raw = localStorage.getItem(STORAGE_KEY);
Expand Down
Loading